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.
Files changed (211) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +479 -0
  3. package/ReactNativeEnrichedMarkdown.podspec +27 -0
  4. package/android/build.gradle +101 -0
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +39 -0
  6. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +21 -0
  7. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
  8. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
  9. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
  10. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +25 -0
  11. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +57 -0
  12. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +1164 -0
  13. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
  14. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
  15. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.cpp +16 -0
  16. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.h +20 -0
  17. package/android/gradle.properties +5 -0
  18. package/android/src/main/AndroidManifest.xml +2 -0
  19. package/android/src/main/baseline-prof.txt +65 -0
  20. package/android/src/main/cpp/jni-adapter.cpp +203 -0
  21. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +153 -0
  22. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +30 -0
  23. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +119 -0
  24. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextPackage.kt +17 -0
  25. package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +165 -0
  26. package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkPressEvent.kt +23 -0
  27. package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +29 -0
  28. package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +48 -0
  29. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockStyleContext.kt +166 -0
  30. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +89 -0
  31. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +105 -0
  32. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +35 -0
  33. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +15 -0
  34. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +26 -0
  35. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +54 -0
  36. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +52 -0
  37. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +15 -0
  38. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +28 -0
  39. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListContextManager.kt +105 -0
  40. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +58 -0
  41. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +69 -0
  42. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +99 -0
  43. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +66 -0
  44. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +95 -0
  45. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +85 -0
  46. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrongRenderer.kt +26 -0
  47. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +29 -0
  48. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +44 -0
  49. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BaseListSpan.kt +136 -0
  50. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +135 -0
  51. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +180 -0
  52. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBlockSpan.kt +196 -0
  53. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeSpan.kt +27 -0
  54. package/android/src/main/java/com/swmansion/enriched/markdown/spans/EmphasisSpan.kt +34 -0
  55. package/android/src/main/java/com/swmansion/enriched/markdown/spans/HeadingSpan.kt +38 -0
  56. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +320 -0
  57. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +36 -0
  58. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +37 -0
  59. package/android/src/main/java/com/swmansion/enriched/markdown/spans/MarginBottomSpan.kt +76 -0
  60. package/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt +87 -0
  61. package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrongSpan.kt +37 -0
  62. package/android/src/main/java/com/swmansion/enriched/markdown/spans/TextSpan.kt +26 -0
  63. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ThematicBreakSpan.kt +69 -0
  64. package/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt +69 -0
  65. package/android/src/main/java/com/swmansion/enriched/markdown/styles/BaseBlockStyle.kt +10 -0
  66. package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +48 -0
  67. package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +51 -0
  68. package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeStyle.kt +21 -0
  69. package/android/src/main/java/com/swmansion/enriched/markdown/styles/EmphasisStyle.kt +17 -0
  70. package/android/src/main/java/com/swmansion/enriched/markdown/styles/HeadingStyle.kt +29 -0
  71. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +21 -0
  72. package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineImageStyle.kt +17 -0
  73. package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +19 -0
  74. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +54 -0
  75. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +29 -0
  76. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrongStyle.kt +17 -0
  77. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +180 -0
  78. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +75 -0
  79. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ThematicBreakStyle.kt +23 -0
  80. package/android/src/main/java/com/swmansion/enriched/markdown/utils/AsyncDrawable.kt +91 -0
  81. package/android/src/main/java/com/swmansion/enriched/markdown/utils/HTMLGenerator.kt +809 -0
  82. package/android/src/main/java/com/swmansion/enriched/markdown/utils/MarkdownExtractor.kt +365 -0
  83. package/android/src/main/java/com/swmansion/enriched/markdown/utils/SelectionActionMode.kt +139 -0
  84. package/android/src/main/java/com/swmansion/enriched/markdown/utils/Utils.kt +181 -0
  85. package/android/src/main/jni/CMakeLists.txt +82 -0
  86. package/android/src/main/jni/EnrichedMarkdownTextSpec.cpp +21 -0
  87. package/android/src/main/jni/EnrichedMarkdownTextSpec.h +25 -0
  88. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextComponentDescriptor.h +29 -0
  89. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.cpp +45 -0
  90. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.h +21 -0
  91. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +33 -0
  92. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +49 -0
  93. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +9 -0
  94. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +25 -0
  95. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +19 -0
  96. package/cpp/md4c/md4c.c +6492 -0
  97. package/cpp/md4c/md4c.h +402 -0
  98. package/cpp/parser/MD4CParser.cpp +314 -0
  99. package/cpp/parser/MD4CParser.hpp +23 -0
  100. package/cpp/parser/MarkdownASTNode.hpp +49 -0
  101. package/ios/EnrichedMarkdownText.h +18 -0
  102. package/ios/EnrichedMarkdownText.mm +1074 -0
  103. package/ios/attachments/ImageAttachment.h +23 -0
  104. package/ios/attachments/ImageAttachment.m +185 -0
  105. package/ios/attachments/ThematicBreakAttachment.h +15 -0
  106. package/ios/attachments/ThematicBreakAttachment.m +33 -0
  107. package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
  108. package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
  109. package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
  110. package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +25 -0
  111. package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +57 -0
  112. package/ios/generated/EnrichedMarkdownTextSpec/Props.h +1164 -0
  113. package/ios/generated/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +20 -0
  114. package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
  115. package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
  116. package/ios/generated/EnrichedMarkdownTextSpec/States.cpp +16 -0
  117. package/ios/generated/EnrichedMarkdownTextSpec/States.h +20 -0
  118. package/ios/internals/EnrichedMarkdownTextComponentDescriptor.h +19 -0
  119. package/ios/internals/EnrichedMarkdownTextShadowNode.h +43 -0
  120. package/ios/internals/EnrichedMarkdownTextShadowNode.mm +85 -0
  121. package/ios/internals/EnrichedMarkdownTextState.h +24 -0
  122. package/ios/parser/MarkdownASTNode.h +33 -0
  123. package/ios/parser/MarkdownASTNode.m +32 -0
  124. package/ios/parser/MarkdownParser.h +8 -0
  125. package/ios/parser/MarkdownParser.mm +13 -0
  126. package/ios/parser/MarkdownParserBridge.mm +110 -0
  127. package/ios/renderer/AttributedRenderer.h +9 -0
  128. package/ios/renderer/AttributedRenderer.m +119 -0
  129. package/ios/renderer/BlockquoteRenderer.h +7 -0
  130. package/ios/renderer/BlockquoteRenderer.m +159 -0
  131. package/ios/renderer/CodeBlockRenderer.h +10 -0
  132. package/ios/renderer/CodeBlockRenderer.m +89 -0
  133. package/ios/renderer/CodeRenderer.h +10 -0
  134. package/ios/renderer/CodeRenderer.m +60 -0
  135. package/ios/renderer/EmphasisRenderer.h +6 -0
  136. package/ios/renderer/EmphasisRenderer.m +96 -0
  137. package/ios/renderer/HeadingRenderer.h +7 -0
  138. package/ios/renderer/HeadingRenderer.m +98 -0
  139. package/ios/renderer/ImageRenderer.h +12 -0
  140. package/ios/renderer/ImageRenderer.m +62 -0
  141. package/ios/renderer/LinkRenderer.h +7 -0
  142. package/ios/renderer/LinkRenderer.m +69 -0
  143. package/ios/renderer/ListItemRenderer.h +16 -0
  144. package/ios/renderer/ListItemRenderer.m +91 -0
  145. package/ios/renderer/ListRenderer.h +13 -0
  146. package/ios/renderer/ListRenderer.m +67 -0
  147. package/ios/renderer/NodeRenderer.h +8 -0
  148. package/ios/renderer/ParagraphRenderer.h +7 -0
  149. package/ios/renderer/ParagraphRenderer.m +69 -0
  150. package/ios/renderer/RenderContext.h +88 -0
  151. package/ios/renderer/RenderContext.m +248 -0
  152. package/ios/renderer/RendererFactory.h +12 -0
  153. package/ios/renderer/RendererFactory.m +110 -0
  154. package/ios/renderer/StrongRenderer.h +6 -0
  155. package/ios/renderer/StrongRenderer.m +83 -0
  156. package/ios/renderer/TextRenderer.h +6 -0
  157. package/ios/renderer/TextRenderer.m +16 -0
  158. package/ios/renderer/ThematicBreakRenderer.h +5 -0
  159. package/ios/renderer/ThematicBreakRenderer.m +53 -0
  160. package/ios/styles/StyleConfig.h +228 -0
  161. package/ios/styles/StyleConfig.mm +1467 -0
  162. package/ios/utils/BlockquoteBorder.h +20 -0
  163. package/ios/utils/BlockquoteBorder.m +92 -0
  164. package/ios/utils/CodeBackground.h +19 -0
  165. package/ios/utils/CodeBackground.m +191 -0
  166. package/ios/utils/CodeBlockBackground.h +17 -0
  167. package/ios/utils/CodeBlockBackground.m +87 -0
  168. package/ios/utils/EditMenuUtils.h +22 -0
  169. package/ios/utils/EditMenuUtils.m +118 -0
  170. package/ios/utils/FontUtils.h +20 -0
  171. package/ios/utils/FontUtils.m +13 -0
  172. package/ios/utils/HTMLGenerator.h +20 -0
  173. package/ios/utils/HTMLGenerator.m +779 -0
  174. package/ios/utils/LastElementUtils.h +53 -0
  175. package/ios/utils/ListMarkerDrawer.h +15 -0
  176. package/ios/utils/ListMarkerDrawer.m +127 -0
  177. package/ios/utils/MarkdownExtractor.h +17 -0
  178. package/ios/utils/MarkdownExtractor.m +295 -0
  179. package/ios/utils/ParagraphStyleUtils.h +13 -0
  180. package/ios/utils/ParagraphStyleUtils.m +56 -0
  181. package/ios/utils/PasteboardUtils.h +36 -0
  182. package/ios/utils/PasteboardUtils.m +134 -0
  183. package/ios/utils/RTFExportUtils.h +24 -0
  184. package/ios/utils/RTFExportUtils.m +297 -0
  185. package/ios/utils/RuntimeKeys.h +38 -0
  186. package/ios/utils/RuntimeKeys.m +11 -0
  187. package/ios/utils/TextViewLayoutManager.h +14 -0
  188. package/ios/utils/TextViewLayoutManager.mm +113 -0
  189. package/lib/module/EnrichedMarkdownText.js +34 -0
  190. package/lib/module/EnrichedMarkdownText.js.map +1 -0
  191. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +130 -0
  192. package/lib/module/index.js +5 -0
  193. package/lib/module/index.js.map +1 -0
  194. package/lib/module/normalizeMarkdownStyle.js +340 -0
  195. package/lib/module/normalizeMarkdownStyle.js.map +1 -0
  196. package/lib/module/package.json +1 -0
  197. package/lib/typescript/package.json +1 -0
  198. package/lib/typescript/src/EnrichedMarkdownText.d.ts +101 -0
  199. package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -0
  200. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +111 -0
  201. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -0
  202. package/lib/typescript/src/index.d.ts +5 -0
  203. package/lib/typescript/src/index.d.ts.map +1 -0
  204. package/lib/typescript/src/normalizeMarkdownStyle.d.ts +6 -0
  205. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -0
  206. package/package.json +186 -1
  207. package/react-native.config.js +13 -0
  208. package/src/EnrichedMarkdownText.tsx +152 -0
  209. package/src/EnrichedMarkdownTextNativeComponent.ts +130 -0
  210. package/src/index.tsx +7 -0
  211. 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
+ }