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