react-native-enriched-markdown 0.1.0 → 0.1.1
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 +479 -0
- package/ReactNativeEnrichedMarkdown.podspec +27 -0
- package/android/build.gradle +101 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +39 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +21 -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 +24 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +25 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +57 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +1164 -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 +203 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +153 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +119 -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 +165 -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 +29 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +48 -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 +89 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +105 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +35 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +15 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +26 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +54 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +52 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +15 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +28 -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 +58 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +69 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +99 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +66 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +95 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +85 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrongRenderer.kt +26 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +44 -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 +320 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +37 -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/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 +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +48 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +51 -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 +29 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +21 -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 +54 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +29 -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 +180 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +75 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ThematicBreakStyle.kt +23 -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 +809 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/MarkdownExtractor.kt +365 -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 +181 -0
- package/android/src/main/jni/CMakeLists.txt +82 -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 +33 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +49 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +9 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +25 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +19 -0
- package/cpp/md4c/md4c.c +6492 -0
- package/cpp/md4c/md4c.h +402 -0
- package/cpp/parser/MD4CParser.cpp +314 -0
- package/cpp/parser/MD4CParser.hpp +23 -0
- package/cpp/parser/MarkdownASTNode.hpp +49 -0
- package/ios/EnrichedMarkdownText.h +18 -0
- package/ios/EnrichedMarkdownText.mm +1074 -0
- package/ios/attachments/ImageAttachment.h +23 -0
- package/ios/attachments/ImageAttachment.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 +24 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +25 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +57 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.h +1164 -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 +33 -0
- package/ios/parser/MarkdownASTNode.m +32 -0
- package/ios/parser/MarkdownParser.h +8 -0
- package/ios/parser/MarkdownParser.mm +13 -0
- package/ios/parser/MarkdownParserBridge.mm +110 -0
- package/ios/renderer/AttributedRenderer.h +9 -0
- package/ios/renderer/AttributedRenderer.m +119 -0
- package/ios/renderer/BlockquoteRenderer.h +7 -0
- package/ios/renderer/BlockquoteRenderer.m +159 -0
- package/ios/renderer/CodeBlockRenderer.h +10 -0
- package/ios/renderer/CodeBlockRenderer.m +89 -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 +98 -0
- package/ios/renderer/ImageRenderer.h +12 -0
- package/ios/renderer/ImageRenderer.m +62 -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 +91 -0
- package/ios/renderer/ListRenderer.h +13 -0
- package/ios/renderer/ListRenderer.m +67 -0
- package/ios/renderer/NodeRenderer.h +8 -0
- package/ios/renderer/ParagraphRenderer.h +7 -0
- package/ios/renderer/ParagraphRenderer.m +69 -0
- package/ios/renderer/RenderContext.h +88 -0
- package/ios/renderer/RenderContext.m +248 -0
- package/ios/renderer/RendererFactory.h +12 -0
- package/ios/renderer/RendererFactory.m +110 -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/styles/StyleConfig.h +228 -0
- package/ios/styles/StyleConfig.mm +1467 -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 +87 -0
- package/ios/utils/EditMenuUtils.h +22 -0
- package/ios/utils/EditMenuUtils.m +118 -0
- package/ios/utils/FontUtils.h +20 -0
- package/ios/utils/FontUtils.m +13 -0
- package/ios/utils/HTMLGenerator.h +20 -0
- package/ios/utils/HTMLGenerator.m +779 -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/MarkdownExtractor.h +17 -0
- package/ios/utils/MarkdownExtractor.m +295 -0
- package/ios/utils/ParagraphStyleUtils.h +13 -0
- package/ios/utils/ParagraphStyleUtils.m +56 -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 +34 -0
- package/lib/module/EnrichedMarkdownText.js.map +1 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +130 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/normalizeMarkdownStyle.js +340 -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 +101 -0
- package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +111 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +5 -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 +152 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +130 -0
- package/src/index.tsx +7 -0
- package/src/normalizeMarkdownStyle.ts +377 -0
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
factory: RendererFactory,
|
|
16
|
+
) {
|
|
17
|
+
factory.renderWithSpan(builder, { factory.renderChildren(node, builder, onLinkPress) }) { start, end, blockStyle ->
|
|
18
|
+
builder.setSpan(
|
|
19
|
+
EmphasisSpan(factory.styleCache, blockStyle),
|
|
20
|
+
start,
|
|
21
|
+
end,
|
|
22
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
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.HeadingSpan
|
|
6
|
+
import com.swmansion.enriched.markdown.utils.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
|
|
7
|
+
import com.swmansion.enriched.markdown.utils.applyMarginBottom
|
|
8
|
+
import com.swmansion.enriched.markdown.utils.createLineHeightSpan
|
|
9
|
+
|
|
10
|
+
class HeadingRenderer(
|
|
11
|
+
private val config: RendererConfig,
|
|
12
|
+
) : NodeRenderer {
|
|
13
|
+
override fun render(
|
|
14
|
+
node: MarkdownASTNode,
|
|
15
|
+
builder: SpannableStringBuilder,
|
|
16
|
+
onLinkPress: ((String) -> Unit)?,
|
|
17
|
+
factory: RendererFactory,
|
|
18
|
+
) {
|
|
19
|
+
val level = node.getAttribute("level")?.toIntOrNull() ?: 1
|
|
20
|
+
val start = builder.length
|
|
21
|
+
|
|
22
|
+
val headingStyle = config.style.headingStyles[level]!!
|
|
23
|
+
factory.blockStyleContext.setHeadingStyle(headingStyle, level)
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
factory.renderChildren(node, builder, onLinkPress)
|
|
27
|
+
} finally {
|
|
28
|
+
factory.blockStyleContext.clearBlockStyle()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
val end = builder.length
|
|
32
|
+
val contentLength = end - start
|
|
33
|
+
if (contentLength > 0) {
|
|
34
|
+
builder.setSpan(
|
|
35
|
+
HeadingSpan(
|
|
36
|
+
level,
|
|
37
|
+
config.style,
|
|
38
|
+
),
|
|
39
|
+
start,
|
|
40
|
+
end,
|
|
41
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
builder.setSpan(
|
|
45
|
+
createLineHeightSpan(headingStyle.lineHeight),
|
|
46
|
+
start,
|
|
47
|
+
end,
|
|
48
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
applyMarginBottom(builder, start, headingStyle.marginBottom)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
factory: RendererFactory,
|
|
19
|
+
) {
|
|
20
|
+
val imageUrl = node.getAttribute("url") ?: return
|
|
21
|
+
|
|
22
|
+
val isInline = builder.isInlineImage()
|
|
23
|
+
val start = builder.length
|
|
24
|
+
|
|
25
|
+
// 1. Append the placeholder character
|
|
26
|
+
builder.append("\uFFFC")
|
|
27
|
+
val end = builder.length
|
|
28
|
+
|
|
29
|
+
// 2. Create the Span
|
|
30
|
+
val span =
|
|
31
|
+
ImageSpan(
|
|
32
|
+
context = context,
|
|
33
|
+
imageUrl = imageUrl,
|
|
34
|
+
styleConfig = config.style,
|
|
35
|
+
isInline = isInline,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
// 3. Attach it to the builder
|
|
39
|
+
builder.setSpan(
|
|
40
|
+
span,
|
|
41
|
+
start,
|
|
42
|
+
end,
|
|
43
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
// 4. REPORT the span to the collector via the factory
|
|
47
|
+
factory.registerImageSpan(span)
|
|
48
|
+
|
|
49
|
+
// Note: marginBottom for images is handled by ParagraphRenderer when the paragraph contains only an image
|
|
50
|
+
// This ensures consistent spacing behavior and prevents paragraph's marginBottom from affecting images
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
factory: RendererFactory,
|
|
12
|
+
) {
|
|
13
|
+
builder.append("\n")
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
factory: RendererFactory,
|
|
16
|
+
) {
|
|
17
|
+
val url = node.getAttribute("url") ?: return
|
|
18
|
+
|
|
19
|
+
factory.renderWithSpan(builder, { factory.renderChildren(node, builder, onLinkPress) }) { start, end, blockStyle ->
|
|
20
|
+
builder.setSpan(
|
|
21
|
+
LinkSpan(url, onLinkPress, factory.styleCache, blockStyle, factory.context),
|
|
22
|
+
start,
|
|
23
|
+
end,
|
|
24
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -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,58 @@
|
|
|
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
|
+
factory: RendererFactory,
|
|
17
|
+
) {
|
|
18
|
+
val styleContext = factory.blockStyleContext
|
|
19
|
+
val start = builder.length
|
|
20
|
+
val listType = styleContext.listType ?: return
|
|
21
|
+
|
|
22
|
+
// 1. Maintain item state
|
|
23
|
+
if (listType == BlockStyleContext.ListType.ORDERED) {
|
|
24
|
+
styleContext.incrementListItemNumber()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 2. Render Children
|
|
28
|
+
factory.renderChildren(node, builder, onLinkPress)
|
|
29
|
+
|
|
30
|
+
// 3. Normalize whitespace: Ensure the item ends with exactly one newline
|
|
31
|
+
if (builder.length == start || builder.substring(start).isBlank()) return
|
|
32
|
+
|
|
33
|
+
while (builder.length > start && builder.last() == '\n') {
|
|
34
|
+
builder.delete(builder.length - 1, builder.length)
|
|
35
|
+
}
|
|
36
|
+
builder.append("\n")
|
|
37
|
+
|
|
38
|
+
// 4. Calculate Depth and Style
|
|
39
|
+
val depth = styleContext.listDepth - 1
|
|
40
|
+
val listStyle = config.style.listStyle
|
|
41
|
+
|
|
42
|
+
// 5. Apply the correct Span
|
|
43
|
+
val span =
|
|
44
|
+
when (listType) {
|
|
45
|
+
BlockStyleContext.ListType.UNORDERED -> {
|
|
46
|
+
UnorderedListSpan(listStyle, depth, factory.context, factory.styleCache)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
BlockStyleContext.ListType.ORDERED -> {
|
|
50
|
+
OrderedListSpan(listStyle, depth, factory.context, factory.styleCache).apply {
|
|
51
|
+
setItemNumber(styleContext.listItemNumber)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
builder.setSpan(span, start, builder.length, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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.createLineHeightSpan
|
|
8
|
+
|
|
9
|
+
class ListRenderer(
|
|
10
|
+
private val config: RendererConfig,
|
|
11
|
+
private val isOrdered: Boolean,
|
|
12
|
+
) : NodeRenderer {
|
|
13
|
+
override fun render(
|
|
14
|
+
node: MarkdownASTNode,
|
|
15
|
+
builder: SpannableStringBuilder,
|
|
16
|
+
onLinkPress: ((String) -> Unit)?,
|
|
17
|
+
factory: RendererFactory,
|
|
18
|
+
) {
|
|
19
|
+
val start = builder.length
|
|
20
|
+
val listStyle = config.style.listStyle
|
|
21
|
+
val listType = if (isOrdered) BlockStyleContext.ListType.ORDERED else BlockStyleContext.ListType.UNORDERED
|
|
22
|
+
|
|
23
|
+
// 1. Context Lifecycle Management
|
|
24
|
+
val contextManager = ListContextManager(factory.blockStyleContext, config.style)
|
|
25
|
+
val entryState = contextManager.enterList(listType, listStyle)
|
|
26
|
+
|
|
27
|
+
// 2. Nested List Isolation
|
|
28
|
+
if (entryState.previousDepth > 0 && builder.isNotEmpty() && builder.last() != '\n') {
|
|
29
|
+
builder.append("\n")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
factory.renderChildren(node, builder, onLinkPress)
|
|
34
|
+
} finally {
|
|
35
|
+
contextManager.exitList(entryState)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 3. Spacing & Styling
|
|
39
|
+
if (builder.length > start) {
|
|
40
|
+
applyListSpacing(builder, start, entryState.previousDepth, listStyle)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private fun applyListSpacing(
|
|
45
|
+
builder: SpannableStringBuilder,
|
|
46
|
+
start: Int,
|
|
47
|
+
depth: Int,
|
|
48
|
+
style: com.swmansion.enriched.markdown.styles.BaseBlockStyle,
|
|
49
|
+
) {
|
|
50
|
+
// Apply line height to the entire list block
|
|
51
|
+
builder.setSpan(
|
|
52
|
+
createLineHeightSpan(style.lineHeight),
|
|
53
|
+
start,
|
|
54
|
+
builder.length,
|
|
55
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
// Only apply bottom margin for top-level lists
|
|
59
|
+
if (depth == 0 && style.marginBottom > 0) {
|
|
60
|
+
builder.append("\n")
|
|
61
|
+
builder.setSpan(
|
|
62
|
+
MarginBottomSpan(style.marginBottom),
|
|
63
|
+
builder.length - 1,
|
|
64
|
+
builder.length,
|
|
65
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
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.styles.StyleConfig
|
|
8
|
+
|
|
9
|
+
interface NodeRenderer {
|
|
10
|
+
fun render(
|
|
11
|
+
node: MarkdownASTNode,
|
|
12
|
+
builder: SpannableStringBuilder,
|
|
13
|
+
onLinkPress: ((String) -> Unit)?,
|
|
14
|
+
factory: RendererFactory,
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
data class RendererConfig(
|
|
19
|
+
val style: StyleConfig,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
class RendererFactory(
|
|
23
|
+
private val config: RendererConfig,
|
|
24
|
+
val context: Context,
|
|
25
|
+
private val onImageSpanCreated: (ImageSpan) -> Unit,
|
|
26
|
+
) {
|
|
27
|
+
val blockStyleContext = BlockStyleContext()
|
|
28
|
+
|
|
29
|
+
val styleCache = SpanStyleCache(config.style)
|
|
30
|
+
|
|
31
|
+
fun resetForNewRender() {
|
|
32
|
+
blockStyleContext.resetForNewRender()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private val textRenderer = TextRenderer()
|
|
36
|
+
private val lineBreakRenderer = LineBreakRenderer()
|
|
37
|
+
|
|
38
|
+
private val renderers: Map<MarkdownASTNode.NodeType, NodeRenderer> by lazy {
|
|
39
|
+
mapOf(
|
|
40
|
+
MarkdownASTNode.NodeType.Document to DocumentRenderer(),
|
|
41
|
+
MarkdownASTNode.NodeType.Paragraph to ParagraphRenderer(config),
|
|
42
|
+
MarkdownASTNode.NodeType.Heading to HeadingRenderer(config),
|
|
43
|
+
MarkdownASTNode.NodeType.Blockquote to BlockquoteRenderer(config),
|
|
44
|
+
MarkdownASTNode.NodeType.CodeBlock to CodeBlockRenderer(config),
|
|
45
|
+
MarkdownASTNode.NodeType.UnorderedList to ListRenderer(config, isOrdered = false),
|
|
46
|
+
MarkdownASTNode.NodeType.OrderedList to ListRenderer(config, isOrdered = true),
|
|
47
|
+
MarkdownASTNode.NodeType.ListItem to ListItemRenderer(config),
|
|
48
|
+
MarkdownASTNode.NodeType.Text to textRenderer,
|
|
49
|
+
MarkdownASTNode.NodeType.Link to LinkRenderer(config),
|
|
50
|
+
MarkdownASTNode.NodeType.Strong to StrongRenderer(config),
|
|
51
|
+
MarkdownASTNode.NodeType.Emphasis to EmphasisRenderer(config),
|
|
52
|
+
MarkdownASTNode.NodeType.Code to CodeRenderer(config),
|
|
53
|
+
MarkdownASTNode.NodeType.Image to ImageRenderer(config, context),
|
|
54
|
+
MarkdownASTNode.NodeType.LineBreak to lineBreakRenderer,
|
|
55
|
+
MarkdownASTNode.NodeType.ThematicBreak to ThematicBreakRenderer(config),
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Called by ImageRenderer to report a new span to the collector.
|
|
61
|
+
*/
|
|
62
|
+
fun registerImageSpan(span: ImageSpan) {
|
|
63
|
+
onImageSpanCreated(span)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fun getRenderer(node: MarkdownASTNode): NodeRenderer =
|
|
67
|
+
renderers[node.type] ?: run {
|
|
68
|
+
android.util.Log.w("RendererFactory", "No renderer for: ${node.type}")
|
|
69
|
+
textRenderer
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fun renderChildren(
|
|
73
|
+
node: MarkdownASTNode,
|
|
74
|
+
builder: SpannableStringBuilder,
|
|
75
|
+
onLinkPress: ((String) -> Unit)?,
|
|
76
|
+
) {
|
|
77
|
+
node.children.forEach { child ->
|
|
78
|
+
getRenderer(child).render(child, builder, onLinkPress, this)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Improved helper for applying spans to blocks of text.
|
|
84
|
+
*/
|
|
85
|
+
inline fun renderWithSpan(
|
|
86
|
+
builder: SpannableStringBuilder,
|
|
87
|
+
renderContent: () -> Unit,
|
|
88
|
+
applySpan: (start: Int, end: Int, blockStyle: BlockStyle) -> Unit,
|
|
89
|
+
) {
|
|
90
|
+
val start = builder.length
|
|
91
|
+
renderContent()
|
|
92
|
+
val end = builder.length
|
|
93
|
+
|
|
94
|
+
if (end > start) {
|
|
95
|
+
val blockStyle = blockStyleContext.requireBlockStyle()
|
|
96
|
+
applySpan(start, end, blockStyle)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
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.styles.ParagraphStyle
|
|
6
|
+
import com.swmansion.enriched.markdown.utils.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
|
|
7
|
+
import com.swmansion.enriched.markdown.utils.applyMarginBottom
|
|
8
|
+
import com.swmansion.enriched.markdown.utils.containsBlockImage
|
|
9
|
+
import com.swmansion.enriched.markdown.utils.createLineHeightSpan
|
|
10
|
+
import com.swmansion.enriched.markdown.utils.getMarginBottomForParagraph
|
|
11
|
+
|
|
12
|
+
class ParagraphRenderer(
|
|
13
|
+
private val config: RendererConfig,
|
|
14
|
+
) : NodeRenderer {
|
|
15
|
+
override fun render(
|
|
16
|
+
node: MarkdownASTNode,
|
|
17
|
+
builder: SpannableStringBuilder,
|
|
18
|
+
onLinkPress: ((String) -> Unit)?,
|
|
19
|
+
factory: RendererFactory,
|
|
20
|
+
) {
|
|
21
|
+
val context = factory.blockStyleContext
|
|
22
|
+
|
|
23
|
+
// If nested, just render content and a newline
|
|
24
|
+
if (context.isInsideBlockElement()) {
|
|
25
|
+
factory.renderChildren(node, builder, onLinkPress)
|
|
26
|
+
builder.append("\n")
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Top-level paragraph rendering
|
|
31
|
+
val start = builder.length
|
|
32
|
+
val style = config.style.paragraphStyle
|
|
33
|
+
|
|
34
|
+
context.setParagraphStyle(style)
|
|
35
|
+
try {
|
|
36
|
+
factory.renderChildren(node, builder, onLinkPress)
|
|
37
|
+
} finally {
|
|
38
|
+
context.clearBlockStyle()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Apply spans only if content was actually added
|
|
42
|
+
if (builder.length > start) {
|
|
43
|
+
builder.applySpans(node, style, start)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private fun SpannableStringBuilder.applySpans(
|
|
48
|
+
node: MarkdownASTNode,
|
|
49
|
+
style: ParagraphStyle,
|
|
50
|
+
start: Int,
|
|
51
|
+
) {
|
|
52
|
+
val end = length // Current length is the end point
|
|
53
|
+
|
|
54
|
+
if (!node.containsBlockImage()) {
|
|
55
|
+
setSpan(
|
|
56
|
+
createLineHeightSpan(style.lineHeight),
|
|
57
|
+
start,
|
|
58
|
+
end,
|
|
59
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
val margin = getMarginBottomForParagraph(node, style, config.style)
|
|
64
|
+
applyMarginBottom(this, start, margin)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.text.SpannableString
|
|
5
|
+
import android.text.SpannableStringBuilder
|
|
6
|
+
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
7
|
+
import com.swmansion.enriched.markdown.spans.ImageSpan
|
|
8
|
+
import com.swmansion.enriched.markdown.spans.MarginBottomSpan
|
|
9
|
+
import com.swmansion.enriched.markdown.styles.StyleConfig
|
|
10
|
+
|
|
11
|
+
class Renderer {
|
|
12
|
+
private var cachedFactory: RendererFactory? = null
|
|
13
|
+
private var cachedStyle: StyleConfig? = null
|
|
14
|
+
private var cachedContext: Context? = null
|
|
15
|
+
|
|
16
|
+
private val collectedImageSpans = mutableListOf<ImageSpan>()
|
|
17
|
+
|
|
18
|
+
fun configure(
|
|
19
|
+
style: StyleConfig,
|
|
20
|
+
context: Context,
|
|
21
|
+
) {
|
|
22
|
+
if (cachedStyle === style && cachedContext === context) return
|
|
23
|
+
|
|
24
|
+
cachedStyle = style
|
|
25
|
+
cachedContext = context
|
|
26
|
+
cachedFactory =
|
|
27
|
+
RendererFactory(
|
|
28
|
+
RendererConfig(style),
|
|
29
|
+
context,
|
|
30
|
+
) { span -> reportImageSpan(span) }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fun renderDocument(
|
|
34
|
+
document: MarkdownASTNode,
|
|
35
|
+
onLinkPress: ((String) -> Unit)? = null,
|
|
36
|
+
): SpannableString {
|
|
37
|
+
val factory =
|
|
38
|
+
requireNotNull(cachedFactory) {
|
|
39
|
+
"Renderer must be configured with a style before calling renderDocument."
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
factory.resetForNewRender()
|
|
43
|
+
collectedImageSpans.clear()
|
|
44
|
+
|
|
45
|
+
val builder = SpannableStringBuilder()
|
|
46
|
+
|
|
47
|
+
renderNode(document, builder, onLinkPress, factory)
|
|
48
|
+
|
|
49
|
+
// Remove trailing margin from last block element
|
|
50
|
+
removeTrailingMargin(builder)
|
|
51
|
+
|
|
52
|
+
return SpannableString(builder)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Removes trailing margin to eliminate bottom spacing */
|
|
56
|
+
private fun removeTrailingMargin(builder: SpannableStringBuilder) {
|
|
57
|
+
if (builder.isEmpty()) return
|
|
58
|
+
|
|
59
|
+
val spans = builder.getSpans(0, builder.length, MarginBottomSpan::class.java)
|
|
60
|
+
if (spans.isEmpty()) return
|
|
61
|
+
|
|
62
|
+
val lastSpan = spans.maxByOrNull { builder.getSpanEnd(it) } ?: return
|
|
63
|
+
val spanEnd = builder.getSpanEnd(lastSpan)
|
|
64
|
+
|
|
65
|
+
// Remove trailing newlines (added for block spacing)
|
|
66
|
+
while (builder.isNotEmpty() && builder.last() == '\n') {
|
|
67
|
+
builder.delete(builder.length - 1, builder.length)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (spanEnd >= builder.length) {
|
|
71
|
+
builder.removeSpan(lastSpan)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private fun renderNode(
|
|
76
|
+
node: MarkdownASTNode,
|
|
77
|
+
builder: SpannableStringBuilder,
|
|
78
|
+
onLinkPress: ((String) -> Unit)?,
|
|
79
|
+
factory: RendererFactory,
|
|
80
|
+
) {
|
|
81
|
+
factory.getRenderer(node).render(node, builder, onLinkPress, factory)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Internal helper used by the Factory's lambda to collect spans.
|
|
86
|
+
*/
|
|
87
|
+
private fun reportImageSpan(span: ImageSpan) {
|
|
88
|
+
collectedImageSpans.add(span)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Provides the EnrichedMarkdownText with the exact list of spans that need registration.
|
|
93
|
+
*/
|
|
94
|
+
fun getCollectedImageSpans(): List<ImageSpan> = collectedImageSpans
|
|
95
|
+
}
|