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,135 @@
1
+ package com.swmansion.enriched.markdown.spans
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import android.graphics.Canvas
6
+ import android.graphics.Color
7
+ import android.graphics.Paint
8
+ import android.graphics.Typeface
9
+ import android.text.Layout
10
+ import android.text.Spanned
11
+ import android.text.TextPaint
12
+ import android.text.style.LeadingMarginSpan
13
+ import android.text.style.MetricAffectingSpan
14
+ import com.swmansion.enriched.markdown.renderer.BlockStyle
15
+ import com.swmansion.enriched.markdown.renderer.SpanStyleCache
16
+ import com.swmansion.enriched.markdown.styles.BlockquoteStyle
17
+ import com.swmansion.enriched.markdown.utils.applyBlockStyleFont
18
+ import com.swmansion.enriched.markdown.utils.applyColorPreserving
19
+
20
+ class BlockquoteSpan(
21
+ private val blockquoteStyle: BlockquoteStyle,
22
+ val depth: Int,
23
+ private val context: Context,
24
+ private val styleCache: SpanStyleCache,
25
+ ) : MetricAffectingSpan(),
26
+ LeadingMarginSpan {
27
+ private val levelSpacing: Float = blockquoteStyle.borderWidth + blockquoteStyle.gapWidth
28
+ private val blockStyle =
29
+ BlockStyle(
30
+ fontSize = blockquoteStyle.fontSize,
31
+ fontFamily = blockquoteStyle.fontFamily,
32
+ fontWeight = blockquoteStyle.fontWeight,
33
+ color = blockquoteStyle.color,
34
+ )
35
+
36
+ // Cache for shouldSkipDrawing to avoid repeated getSpans() calls during draw passes
37
+ private var cachedText: CharSequence? = null
38
+ private var cachedMaxDepthByPosition = mutableMapOf<Int, Int>()
39
+
40
+ override fun updateMeasureState(tp: TextPaint) = applyTextStyle(tp)
41
+
42
+ override fun updateDrawState(tp: TextPaint) = applyTextStyle(tp)
43
+
44
+ override fun getLeadingMargin(first: Boolean): Int = levelSpacing.toInt()
45
+
46
+ override fun drawLeadingMargin(
47
+ c: Canvas,
48
+ p: Paint,
49
+ x: Int,
50
+ dir: Int,
51
+ top: Int,
52
+ baseline: Int,
53
+ bottom: Int,
54
+ text: CharSequence?,
55
+ start: Int,
56
+ end: Int,
57
+ first: Boolean,
58
+ layout: Layout?,
59
+ ) {
60
+ // Essential check from original: only the deepest span draws to prevent over-rendering background
61
+ if (shouldSkipDrawing(text, start)) return
62
+
63
+ drawBackground(c, top, bottom, layout)
64
+
65
+ val borderPaint = configureBorderPaint()
66
+ val borderTop = top.toFloat()
67
+ val borderBottom = bottom.toFloat()
68
+ val containerLeft = layout?.getLineLeft(0) ?: 0f
69
+
70
+ for (level in 0..depth) {
71
+ val borderX = containerLeft + (levelSpacing * level * dir)
72
+ val borderRight = borderX + (blockquoteStyle.borderWidth * dir)
73
+ c.drawRect(borderX, borderTop, borderRight, borderBottom, borderPaint)
74
+ }
75
+ }
76
+
77
+ @SuppressLint("WrongConstant") // Result of mask is always valid: 0, 1, 2, or 3
78
+ private fun applyTextStyle(tp: TextPaint) {
79
+ tp.textSize = blockStyle.fontSize
80
+ val preserved = (tp.typeface?.style ?: 0) and BOLD_ITALIC_MASK
81
+ tp.applyBlockStyleFont(blockStyle, context)
82
+ if (preserved != 0) {
83
+ tp.typeface = Typeface.create(tp.typeface ?: Typeface.DEFAULT, preserved)
84
+ }
85
+ tp.applyColorPreserving(blockStyle.color, *styleCache.colorsToPreserve)
86
+ }
87
+
88
+ companion object {
89
+ private const val BOLD_ITALIC_MASK = Typeface.BOLD or Typeface.ITALIC
90
+
91
+ private val sharedBorderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
92
+ private val sharedBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
93
+ }
94
+
95
+ private fun configureBorderPaint(): Paint =
96
+ sharedBorderPaint.apply {
97
+ color = blockquoteStyle.borderColor
98
+ }
99
+
100
+ private fun configureBackgroundPaint(bgColor: Int): Paint =
101
+ sharedBackgroundPaint.apply {
102
+ color = bgColor
103
+ }
104
+
105
+ private fun shouldSkipDrawing(
106
+ text: CharSequence?,
107
+ start: Int,
108
+ ): Boolean {
109
+ if (text !is Spanned) return false
110
+
111
+ if (cachedText !== text) {
112
+ cachedText = text
113
+ cachedMaxDepthByPosition.clear()
114
+ }
115
+
116
+ val maxDepth =
117
+ cachedMaxDepthByPosition.getOrPut(start) {
118
+ val spans = text.getSpans(start, start + 1, BlockquoteSpan::class.java)
119
+ spans.maxOfOrNull { it.depth } ?: -1
120
+ }
121
+
122
+ return maxDepth > depth
123
+ }
124
+
125
+ private fun drawBackground(
126
+ c: Canvas,
127
+ top: Int,
128
+ bottom: Int,
129
+ layout: Layout?,
130
+ ) {
131
+ val bgColor = blockquoteStyle.backgroundColor?.takeIf { it != Color.TRANSPARENT } ?: return
132
+ val backgroundPaint = configureBackgroundPaint(bgColor)
133
+ c.drawRect(0f, top.toFloat(), layout?.width?.toFloat() ?: 0f, bottom.toFloat(), backgroundPaint)
134
+ }
135
+ }
@@ -0,0 +1,180 @@
1
+ package com.swmansion.enriched.markdown.spans
2
+
3
+ import android.graphics.Canvas
4
+ import android.graphics.Paint
5
+ import android.graphics.Path
6
+ import android.graphics.RectF
7
+ import android.text.Spanned
8
+ import android.text.StaticLayout
9
+ import android.text.TextPaint
10
+ import android.text.style.LineBackgroundSpan
11
+ import com.swmansion.enriched.markdown.styles.StyleConfig
12
+ import kotlin.math.max
13
+ import kotlin.math.min
14
+
15
+ class CodeBackgroundSpan(
16
+ private val styleConfig: StyleConfig,
17
+ ) : LineBackgroundSpan {
18
+ companion object {
19
+ private const val CORNER_RADIUS = 6.0f
20
+ private const val BORDER_WIDTH = 1.0f
21
+
22
+ private val sharedBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
23
+ private val sharedBorderPaint =
24
+ Paint(Paint.ANTI_ALIAS_FLAG).apply {
25
+ style = Paint.Style.STROKE
26
+ strokeWidth = BORDER_WIDTH
27
+ strokeJoin = Paint.Join.ROUND
28
+ strokeCap = Paint.Cap.ROUND
29
+ }
30
+ }
31
+
32
+ // Reusable drawing objects per instance
33
+ private val rect = RectF()
34
+ private val path = Path()
35
+
36
+ override fun drawBackground(
37
+ canvas: Canvas,
38
+ p: Paint,
39
+ left: Int,
40
+ right: Int,
41
+ top: Int,
42
+ baseline: Int,
43
+ bottom: Int,
44
+ text: CharSequence,
45
+ start: Int,
46
+ end: Int,
47
+ lineNum: Int,
48
+ ) {
49
+ if (text !is Spanned) return
50
+
51
+ val spanStart = text.getSpanStart(this)
52
+ val spanEnd = text.getSpanEnd(this)
53
+ if (spanStart !in 0 until spanEnd) return
54
+
55
+ // 1. Determine relative positioning
56
+ val isFirst = spanStart >= start
57
+ val isLast = spanEnd <= end
58
+
59
+ // 2. Calculate coordinates
60
+ val finalBottom = adjustBottomForMargin(text, end, bottom)
61
+ val startX = if (isFirst) getHorizontalOffset(text, start, end, spanStart, p) + left else left.toFloat()
62
+ val endX = if (isLast) getHorizontalOffset(text, start, end, spanEnd, p) + left else right.toFloat()
63
+
64
+ rect.set(min(startX, endX), top.toFloat(), max(startX, endX), finalBottom.toFloat())
65
+
66
+ // 3. Apply Style
67
+ val codeStyle = styleConfig.codeStyle
68
+ sharedBackgroundPaint.color = codeStyle.backgroundColor
69
+ sharedBorderPaint.color = codeStyle.borderColor
70
+
71
+ drawShapes(canvas, isFirst, isLast)
72
+ }
73
+
74
+ private fun getHorizontalOffset(
75
+ text: CharSequence,
76
+ lineStart: Int,
77
+ lineEnd: Int,
78
+ index: Int,
79
+ paint: Paint,
80
+ ): Float {
81
+ if (index <= lineStart) return 0f
82
+ val lineText = text.subSequence(lineStart, lineEnd)
83
+ val textPaint = paint as? TextPaint ?: TextPaint(paint)
84
+ val layout = StaticLayout.Builder.obtain(lineText, 0, lineText.length, textPaint, 10000).build()
85
+ return layout.getPrimaryHorizontal(index - lineStart)
86
+ }
87
+
88
+ private fun drawShapes(
89
+ canvas: Canvas,
90
+ isFirst: Boolean,
91
+ isLast: Boolean,
92
+ ) {
93
+ val radii = createRadii(isFirst, isLast)
94
+
95
+ path.reset()
96
+ path.addRoundRect(rect, radii, Path.Direction.CW)
97
+ canvas.drawPath(path, sharedBackgroundPaint)
98
+
99
+ if (isFirst && isLast) {
100
+ canvas.drawPath(path, sharedBorderPaint)
101
+ } else {
102
+ drawOpenBorders(canvas, isFirst, isLast)
103
+ }
104
+ }
105
+
106
+ private fun drawOpenBorders(
107
+ canvas: Canvas,
108
+ isFirst: Boolean,
109
+ isLast: Boolean,
110
+ ) {
111
+ val r = CORNER_RADIUS
112
+ path.reset()
113
+
114
+ if (isFirst) {
115
+ path.moveTo(rect.right, rect.top)
116
+ path.lineTo(rect.left + r, rect.top)
117
+ path.quadTo(rect.left, rect.top, rect.left, rect.top + r)
118
+ path.lineTo(rect.left, rect.bottom - r)
119
+ path.quadTo(rect.left, rect.bottom, rect.left + r, rect.bottom)
120
+ path.lineTo(rect.right, rect.bottom)
121
+ } else if (isLast) {
122
+ path.moveTo(rect.left, rect.top)
123
+ path.lineTo(rect.right - r, rect.top)
124
+ path.quadTo(rect.right, rect.top, rect.right, rect.top + r)
125
+ path.lineTo(rect.right, rect.bottom - r)
126
+ path.quadTo(rect.right, rect.bottom, rect.right - r, rect.bottom)
127
+ path.lineTo(rect.left, rect.bottom)
128
+ } else {
129
+ path.moveTo(rect.left, rect.top)
130
+ path.lineTo(rect.right, rect.top)
131
+ path.moveTo(rect.left, rect.bottom)
132
+ path.lineTo(rect.right, rect.bottom)
133
+ }
134
+ canvas.drawPath(path, sharedBorderPaint)
135
+ }
136
+
137
+ private fun createRadii(
138
+ isFirst: Boolean,
139
+ isLast: Boolean,
140
+ ) = when {
141
+ isFirst && isLast -> {
142
+ floatArrayOf(
143
+ CORNER_RADIUS,
144
+ CORNER_RADIUS,
145
+ CORNER_RADIUS,
146
+ CORNER_RADIUS,
147
+ CORNER_RADIUS,
148
+ CORNER_RADIUS,
149
+ CORNER_RADIUS,
150
+ CORNER_RADIUS,
151
+ )
152
+ }
153
+
154
+ isFirst -> {
155
+ floatArrayOf(CORNER_RADIUS, CORNER_RADIUS, 0f, 0f, 0f, 0f, CORNER_RADIUS, CORNER_RADIUS)
156
+ }
157
+
158
+ isLast -> {
159
+ floatArrayOf(0f, 0f, CORNER_RADIUS, CORNER_RADIUS, CORNER_RADIUS, CORNER_RADIUS, 0f, 0f)
160
+ }
161
+
162
+ else -> {
163
+ floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
164
+ }
165
+ }
166
+
167
+ private fun adjustBottomForMargin(
168
+ text: Spanned,
169
+ lineEnd: Int,
170
+ bottom: Int,
171
+ ): Int {
172
+ if (lineEnd <= 0 || lineEnd > text.length || text[lineEnd - 1] != '\n') return bottom
173
+ val marginSpans = text.getSpans(lineEnd - 1, lineEnd, MarginBottomSpan::class.java)
174
+ var adjusted = bottom
175
+ for (span in marginSpans) {
176
+ if (text.getSpanEnd(span) == lineEnd) adjusted -= span.marginBottom.toInt()
177
+ }
178
+ return adjusted
179
+ }
180
+ }
@@ -0,0 +1,196 @@
1
+ package com.swmansion.enriched.markdown.spans
2
+
3
+ import android.content.Context
4
+ import android.graphics.Canvas
5
+ import android.graphics.Paint
6
+ import android.graphics.Path
7
+ import android.graphics.RectF
8
+ import android.text.Layout
9
+ import android.text.Spanned
10
+ import android.text.TextPaint
11
+ import android.text.style.LeadingMarginSpan
12
+ import android.text.style.LineBackgroundSpan
13
+ import android.text.style.MetricAffectingSpan
14
+ import androidx.core.graphics.withSave
15
+ import com.swmansion.enriched.markdown.renderer.BlockStyle
16
+ import com.swmansion.enriched.markdown.renderer.SpanStyleCache
17
+ import com.swmansion.enriched.markdown.styles.CodeBlockStyle
18
+ import com.swmansion.enriched.markdown.utils.applyBlockStyleFont
19
+ import com.swmansion.enriched.markdown.utils.applyColorPreserving
20
+
21
+ class CodeBlockSpan(
22
+ private val codeBlockStyle: CodeBlockStyle,
23
+ private val context: Context,
24
+ private val styleCache: SpanStyleCache,
25
+ ) : MetricAffectingSpan(),
26
+ LineBackgroundSpan,
27
+ LeadingMarginSpan {
28
+ private val blockStyle =
29
+ BlockStyle(
30
+ fontSize = codeBlockStyle.fontSize,
31
+ fontFamily = codeBlockStyle.fontFamily,
32
+ fontWeight = codeBlockStyle.fontWeight,
33
+ color = codeBlockStyle.color,
34
+ )
35
+
36
+ private val path = Path()
37
+ private val rect = RectF()
38
+ private val arcRect = RectF()
39
+ private val radiiArray = FloatArray(8)
40
+
41
+ companion object {
42
+ private val sharedBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
43
+ private val sharedBorderPaint =
44
+ Paint(Paint.ANTI_ALIAS_FLAG).apply {
45
+ style = Paint.Style.STROKE
46
+ strokeCap = Paint.Cap.BUTT
47
+ strokeJoin = Paint.Join.ROUND
48
+ }
49
+ }
50
+
51
+ private fun configureBackgroundPaint(): Paint =
52
+ sharedBackgroundPaint.apply {
53
+ color = codeBlockStyle.backgroundColor
54
+ }
55
+
56
+ private fun configureBorderPaint(): Paint =
57
+ sharedBorderPaint.apply {
58
+ strokeWidth = codeBlockStyle.borderWidth
59
+ color = codeBlockStyle.borderColor
60
+ }
61
+
62
+ override fun getLeadingMargin(first: Boolean): Int = codeBlockStyle.padding.toInt()
63
+
64
+ override fun drawLeadingMargin(
65
+ c: Canvas?,
66
+ p: Paint?,
67
+ x: Int,
68
+ dir: Int,
69
+ top: Int,
70
+ baseline: Int,
71
+ bottom: Int,
72
+ text: CharSequence?,
73
+ start: Int,
74
+ end: Int,
75
+ first: Boolean,
76
+ layout: Layout?,
77
+ ) { /* Leading margin is handled by getLeadingMargin */ }
78
+
79
+ override fun updateMeasureState(tp: TextPaint) = applyTextStyle(tp)
80
+
81
+ override fun updateDrawState(tp: TextPaint) = applyTextStyle(tp)
82
+
83
+ override fun drawBackground(
84
+ canvas: Canvas,
85
+ p: Paint,
86
+ left: Int,
87
+ right: Int,
88
+ top: Int,
89
+ baseline: Int,
90
+ bottom: Int,
91
+ text: CharSequence,
92
+ start: Int,
93
+ end: Int,
94
+ lineNum: Int,
95
+ ) {
96
+ if (text !is Spanned) return
97
+
98
+ val spanStart = text.getSpanStart(this)
99
+ val spanEnd = text.getSpanEnd(this)
100
+ if (spanStart !in 0 until spanEnd) return
101
+
102
+ val isFirstLine = start == spanStart
103
+ val isLastLine = end == spanEnd || (spanEnd <= end && (spanEnd == text.length || text[spanEnd - 1] == '\n'))
104
+
105
+ val inset = codeBlockStyle.borderWidth / 2f
106
+
107
+ rect.set(
108
+ left.toFloat() + inset,
109
+ top.toFloat() + (if (isFirstLine) inset else 0f),
110
+ right.toFloat() - inset,
111
+ bottom.toFloat() - (if (isLastLine) inset else 0f),
112
+ )
113
+
114
+ val radius = codeBlockStyle.borderRadius
115
+ val adjRadius = if (radius > inset) radius - inset else radius
116
+
117
+ // Reset and fill radii array based on boundary state
118
+ radiiArray.fill(0f)
119
+ if (isFirstLine) {
120
+ radiiArray[0] = adjRadius
121
+ radiiArray[1] = adjRadius // Top-Left
122
+ radiiArray[2] = adjRadius
123
+ radiiArray[3] = adjRadius // Top-Right
124
+ }
125
+ if (isLastLine) {
126
+ radiiArray[4] = adjRadius
127
+ radiiArray[5] = adjRadius // Bottom-Right
128
+ radiiArray[6] = adjRadius
129
+ radiiArray[7] = adjRadius // Bottom-Left
130
+ }
131
+
132
+ path.reset()
133
+ path.addRoundRect(rect, radiiArray, Path.Direction.CW)
134
+
135
+ val backgroundPaint = configureBackgroundPaint()
136
+ val borderPaint = configureBorderPaint()
137
+
138
+ canvas.withSave {
139
+ drawPath(path, backgroundPaint)
140
+
141
+ if (codeBlockStyle.borderWidth > 0) {
142
+ val bLeft = rect.left
143
+ val bRight = rect.right
144
+ val bTop = rect.top
145
+ val bBottom = rect.bottom
146
+
147
+ when {
148
+ // Case: Single-line code block
149
+ isFirstLine && isLastLine -> {
150
+ drawPath(path, borderPaint)
151
+ }
152
+
153
+ // Case: Top of a multi-line block
154
+ isFirstLine -> {
155
+ drawLine(bLeft + adjRadius, bTop, bRight - adjRadius, bTop, borderPaint)
156
+ drawLine(bLeft, bTop + adjRadius, bLeft, bBottom, borderPaint)
157
+ drawLine(bRight, bTop + adjRadius, bRight, bBottom, borderPaint)
158
+
159
+ arcRect.set(bLeft, bTop, bLeft + 2 * adjRadius, bTop + 2 * adjRadius)
160
+ drawArc(arcRect, 180f, 90f, false, borderPaint)
161
+
162
+ arcRect.set(bRight - 2 * adjRadius, bTop, bRight, bTop + 2 * adjRadius)
163
+ drawArc(arcRect, 270f, 90f, false, borderPaint)
164
+ }
165
+
166
+ // Case: Bottom of a multi-line block
167
+ isLastLine -> {
168
+ drawLine(bLeft + adjRadius, bBottom, bRight - adjRadius, bBottom, borderPaint)
169
+ drawLine(bLeft, bTop, bLeft, bBottom - adjRadius, borderPaint)
170
+ drawLine(bRight, bTop, bRight, bBottom - adjRadius, borderPaint)
171
+
172
+ arcRect.set(bLeft, bBottom - 2 * adjRadius, bLeft + 2 * adjRadius, bBottom)
173
+ drawArc(arcRect, 90f, 90f, false, borderPaint)
174
+
175
+ arcRect.set(bRight - 2 * adjRadius, bBottom - 2 * adjRadius, bRight, bBottom)
176
+ drawArc(arcRect, 0f, 90f, false, borderPaint)
177
+ }
178
+
179
+ // Case: Middle lines only need vertical sides
180
+ else -> {
181
+ drawLine(bLeft, bTop, bLeft, bBottom, borderPaint)
182
+ drawLine(bRight, bTop, bRight, bBottom, borderPaint)
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ private fun applyTextStyle(tp: TextPaint) {
190
+ tp.textSize = blockStyle.fontSize
191
+
192
+ tp.applyBlockStyleFont(blockStyle, context)
193
+
194
+ tp.applyColorPreserving(blockStyle.color, *styleCache.colorsToPreserve)
195
+ }
196
+ }
@@ -0,0 +1,27 @@
1
+ package com.swmansion.enriched.markdown.spans
2
+
3
+ import android.graphics.Typeface
4
+ import android.text.TextPaint
5
+ import android.text.style.MetricAffectingSpan
6
+ import com.swmansion.enriched.markdown.renderer.BlockStyle
7
+ import com.swmansion.enriched.markdown.renderer.SpanStyleCache
8
+
9
+ class CodeSpan(
10
+ private val styleCache: SpanStyleCache,
11
+ private val blockStyle: BlockStyle,
12
+ ) : MetricAffectingSpan() {
13
+ override fun updateDrawState(tp: TextPaint) {
14
+ applyMonospacedFont(tp)
15
+ tp.color = styleCache.codeColor
16
+ }
17
+
18
+ override fun updateMeasureState(tp: TextPaint) {
19
+ applyMonospacedFont(tp)
20
+ }
21
+
22
+ private fun applyMonospacedFont(paint: TextPaint) {
23
+ paint.textSize = blockStyle.fontSize * 0.85f
24
+ val preservedStyle = (paint.typeface?.style ?: 0) and (Typeface.BOLD or Typeface.ITALIC)
25
+ paint.typeface = SpanStyleCache.getMonospaceTypeface(preservedStyle)
26
+ }
27
+ }
@@ -0,0 +1,34 @@
1
+ package com.swmansion.enriched.markdown.spans
2
+
3
+ import android.graphics.Typeface
4
+ import android.text.TextPaint
5
+ import android.text.style.MetricAffectingSpan
6
+ import com.swmansion.enriched.markdown.renderer.BlockStyle
7
+ import com.swmansion.enriched.markdown.renderer.SpanStyleCache
8
+ import com.swmansion.enriched.markdown.utils.applyColorPreserving
9
+
10
+ class EmphasisSpan(
11
+ private val styleCache: SpanStyleCache,
12
+ private val blockStyle: BlockStyle,
13
+ ) : MetricAffectingSpan() {
14
+ override fun updateDrawState(tp: TextPaint) {
15
+ applyEmphasisStyle(tp)
16
+ applyEmphasisColor(tp)
17
+ }
18
+
19
+ override fun updateMeasureState(tp: TextPaint) {
20
+ applyEmphasisStyle(tp)
21
+ }
22
+
23
+ private fun applyEmphasisStyle(tp: TextPaint) {
24
+ val currentTypeface = tp.typeface ?: Typeface.DEFAULT
25
+ val isBold = (currentTypeface.style) and Typeface.BOLD != 0
26
+ val style = if (isBold) Typeface.BOLD_ITALIC else Typeface.ITALIC
27
+ tp.typeface = Typeface.create(currentTypeface, style)
28
+ }
29
+
30
+ private fun applyEmphasisColor(tp: TextPaint) {
31
+ val colorToUse = styleCache.getEmphasisColorFor(blockStyle.color, tp.color)
32
+ tp.applyColorPreserving(colorToUse, *styleCache.colorsToPreserve)
33
+ }
34
+ }
@@ -0,0 +1,38 @@
1
+ package com.swmansion.enriched.markdown.spans
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.graphics.Typeface
5
+ import android.text.TextPaint
6
+ import android.text.style.MetricAffectingSpan
7
+ import com.swmansion.enriched.markdown.styles.StyleConfig
8
+
9
+ class HeadingSpan(
10
+ val level: Int,
11
+ styleConfig: StyleConfig,
12
+ ) : MetricAffectingSpan() {
13
+ private val fontSize: Float = styleConfig.headingStyles[level]!!.fontSize
14
+ private val color: Int = styleConfig.headingStyles[level]!!.color
15
+ private val cachedTypeface: Typeface? = styleConfig.headingTypefaces[level]
16
+
17
+ override fun updateDrawState(tp: TextPaint) {
18
+ applyHeadingStyle(tp)
19
+ tp.color = color
20
+ }
21
+
22
+ override fun updateMeasureState(tp: TextPaint) {
23
+ applyHeadingStyle(tp)
24
+ }
25
+
26
+ @SuppressLint("WrongConstant") // Result of mask is always valid: 0, 1, 2, or 3
27
+ private fun applyHeadingStyle(tp: TextPaint) {
28
+ tp.textSize = fontSize
29
+ cachedTypeface?.let { base ->
30
+ val preserved = (tp.typeface?.style ?: 0) and BOLD_ITALIC_MASK
31
+ tp.typeface = if (preserved != 0) Typeface.create(base, preserved) else base
32
+ }
33
+ }
34
+
35
+ companion object {
36
+ private const val BOLD_ITALIC_MASK = Typeface.BOLD or Typeface.ITALIC
37
+ }
38
+ }