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,152 @@
1
+ #import "AttributedRenderer.h"
2
+ #import "CodeBlockBackground.h"
3
+ #import "LastElementUtils.h"
4
+ #import "MarkdownASTNode.h"
5
+ #import "NodeRenderer.h"
6
+ #import "RenderContext.h"
7
+ #import "RendererFactory.h"
8
+ #import "StyleConfig.h"
9
+
10
+ @implementation AttributedRenderer {
11
+ StyleConfig *_config;
12
+ RendererFactory *_rendererFactory;
13
+ CGFloat _lastElementMarginBottom;
14
+ BOOL _allowTrailingMargin;
15
+ }
16
+
17
+ - (instancetype)initWithConfig:(StyleConfig *)config
18
+ {
19
+ self = [super init];
20
+ if (self) {
21
+ _config = config;
22
+ _rendererFactory = [[RendererFactory alloc] initWithConfig:config];
23
+ _lastElementMarginBottom = 0.0;
24
+ _allowTrailingMargin = NO;
25
+ }
26
+ return self;
27
+ }
28
+
29
+ /**
30
+ * Entry point for rendering a Markdown AST.
31
+ * Sets the baseline global style and initiates the recursive traversal.
32
+ */
33
+ - (NSMutableAttributedString *)renderRoot:(MarkdownASTNode *)root context:(RenderContext *)context
34
+ {
35
+ if (!root)
36
+ return [[NSMutableAttributedString alloc] init];
37
+
38
+ // 1. Establish the global baseline style.
39
+ // This ensures that leaf nodes (like Text) have valid attributes if they appear at the root.
40
+ [context setBlockStyle:BlockTypeParagraph font:_config.paragraphFont color:_config.paragraphColor headingLevel:0];
41
+
42
+ NSMutableAttributedString *output = [[NSMutableAttributedString alloc] init];
43
+
44
+ // 2. Iterate through root children.
45
+ // We skip the 'Root' node itself as it is a container, not a renderable element.
46
+ for (MarkdownASTNode *node in root.children) {
47
+ [self renderNodeRecursive:node into:output context:context];
48
+ }
49
+
50
+ // 3. Remove trailing paragraph spacing from last block element
51
+ // Reset lastElementMarginBottom before processing
52
+ _lastElementMarginBottom = 0.0;
53
+ [self removeTrailingSpacing:output];
54
+
55
+ // 4. Cleanup global state to prevent side effects in subsequent renders.
56
+ [context clearBlockStyle];
57
+
58
+ return output;
59
+ }
60
+
61
+ /// Removes trailing margin spacing while preserving code block padding
62
+ - (void)removeTrailingSpacing:(NSMutableAttributedString *)output
63
+ {
64
+ if (output.length == 0)
65
+ return;
66
+
67
+ // Find the last non-newline character
68
+ NSRange lastContent = [output.string rangeOfCharacterFromSet:[NSCharacterSet.newlineCharacterSet invertedSet]
69
+ options:NSBackwardsSearch];
70
+ if (lastContent.location == NSNotFound)
71
+ return;
72
+
73
+ // 1. Capture the Margin Bottom (Scanning from last content to end)
74
+ _lastElementMarginBottom = 0.0;
75
+ for (NSUInteger i = lastContent.location; i < output.length;) {
76
+ NSRange attrRange;
77
+ NSParagraphStyle *style = [output attribute:NSParagraphStyleAttributeName atIndex:i effectiveRange:&attrRange];
78
+ if (style) {
79
+ _lastElementMarginBottom = MAX(_lastElementMarginBottom, style.paragraphSpacing);
80
+ }
81
+ i = NSMaxRange(attrRange);
82
+ }
83
+
84
+ // 2. Trim trailing characters
85
+ NSUInteger logicalEnd = NSMaxRange(lastContent);
86
+ if (isLastElementCodeBlock(output)) {
87
+ NSRange codeRange;
88
+ [output attribute:CodeBlockAttributeName atIndex:lastContent.location effectiveRange:&codeRange];
89
+ logicalEnd = NSMaxRange(codeRange);
90
+ }
91
+
92
+ if (logicalEnd < output.length) {
93
+ [output deleteCharactersInRange:NSMakeRange(logicalEnd, output.length - logicalEnd)];
94
+ }
95
+
96
+ // 3. Zero out internal spacing for the last element (if not a code block)
97
+ if (!isLastElementCodeBlock(output)) {
98
+ NSRange styleRange;
99
+ NSParagraphStyle *style = [output attribute:NSParagraphStyleAttributeName
100
+ atIndex:lastContent.location
101
+ effectiveRange:&styleRange];
102
+
103
+ if (style) {
104
+ NSMutableParagraphStyle *mutableStyle = [style mutableCopy];
105
+ mutableStyle.paragraphSpacing = 0;
106
+ mutableStyle.paragraphSpacingBefore = 0;
107
+
108
+ if (isLastElementImage(output)) {
109
+ mutableStyle.lineSpacing = 0;
110
+ }
111
+
112
+ NSRange safeRange = NSIntersectionRange(styleRange, NSMakeRange(0, output.length));
113
+ [output addAttribute:NSParagraphStyleAttributeName value:mutableStyle range:safeRange];
114
+ }
115
+ }
116
+ }
117
+
118
+ - (void)setAllowTrailingMargin:(BOOL)allow
119
+ {
120
+ _allowTrailingMargin = allow;
121
+ }
122
+
123
+ - (CGFloat)getLastElementMarginBottom
124
+ {
125
+ return _lastElementMarginBottom;
126
+ }
127
+
128
+ /**
129
+ * Orchestrates the recursive traversal of the AST.
130
+ * If a specialized renderer exists for a node type, it takes full control.
131
+ */
132
+ - (void)renderNodeRecursive:(MarkdownASTNode *)node
133
+ into:(NSMutableAttributedString *)out
134
+ context:(RenderContext *)context
135
+ {
136
+ if (!node)
137
+ return;
138
+
139
+ id<NodeRenderer> renderer = [_rendererFactory rendererForNodeType:node.type];
140
+
141
+ if (renderer) {
142
+ // Specialized renderers (e.g., Strong, Link, Heading) handle their own sub-trees.
143
+ [renderer renderNode:node into:out context:context];
144
+ } else {
145
+ // Fallback: Default to deep-first traversal for unhandled container nodes.
146
+ for (MarkdownASTNode *child in node.children) {
147
+ [self renderNodeRecursive:child into:out context:context];
148
+ }
149
+ }
150
+ }
151
+
152
+ @end
@@ -0,0 +1,7 @@
1
+ #import "MarkdownASTNode.h"
2
+ #import "NodeRenderer.h"
3
+ #import "RenderContext.h"
4
+
5
+ @interface BlockquoteRenderer : NSObject <NodeRenderer>
6
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config;
7
+ @end
@@ -0,0 +1,160 @@
1
+ #import "BlockquoteRenderer.h"
2
+ #import "BlockquoteBorder.h"
3
+ #import "FontUtils.h"
4
+ #import "MarkdownASTNode.h"
5
+ #import "ParagraphStyleUtils.h"
6
+ #import "RendererFactory.h"
7
+ #import "StyleConfig.h"
8
+
9
+ static NSString *const kNestedInfoDepthKey = @"depth";
10
+ static NSString *const kNestedInfoRangeKey = @"range";
11
+
12
+ @implementation BlockquoteRenderer {
13
+ RendererFactory *_rendererFactory;
14
+ StyleConfig *_config;
15
+ }
16
+
17
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
18
+ {
19
+ if (self = [super init]) {
20
+ _rendererFactory = rendererFactory;
21
+ _config = (StyleConfig *)config;
22
+ }
23
+ return self;
24
+ }
25
+
26
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
27
+ {
28
+ NSInteger currentDepth = context.blockquoteDepth;
29
+ context.blockquoteDepth = currentDepth + 1;
30
+
31
+ [context setBlockStyle:BlockTypeBlockquote
32
+ font:[_config blockquoteFont]
33
+ color:[_config blockquoteColor]
34
+ headingLevel:0];
35
+
36
+ NSUInteger start = output.length;
37
+ @try {
38
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
39
+ } @finally {
40
+ [context clearBlockStyle];
41
+ context.blockquoteDepth = currentDepth;
42
+ }
43
+
44
+ NSUInteger end = output.length;
45
+ if (end <= start) {
46
+ return;
47
+ }
48
+
49
+ [self applyStylingAndSpacing:output start:start end:end currentDepth:currentDepth];
50
+ }
51
+
52
+ #pragma mark - Styling and Spacing
53
+
54
+ - (void)applyStylingAndSpacing:(NSMutableAttributedString *)output
55
+ start:(NSUInteger)start
56
+ end:(NSUInteger)end
57
+ currentDepth:(NSInteger)currentDepth
58
+ {
59
+ NSUInteger contentStart = start;
60
+ if (currentDepth == 0) {
61
+ contentStart += applyBlockSpacingBefore(output, start, [_config blockquoteMarginTop]);
62
+ }
63
+
64
+ NSRange blockquoteRange = NSMakeRange(contentStart, end - start);
65
+ CGFloat levelSpacing = [_config blockquoteBorderWidth] + [_config blockquoteGapWidth];
66
+ NSArray<NSDictionary *> *nestedInfo = [self collectNestedBlockquotes:output range:blockquoteRange depth:currentDepth];
67
+
68
+ // Apply base styling (indentation, depth, background, line height)
69
+ [self applyBaseBlockquoteStyle:output
70
+ range:blockquoteRange
71
+ depth:currentDepth
72
+ levelSpacing:levelSpacing
73
+ backgroundColor:[_config blockquoteBackgroundColor]
74
+ lineHeight:[_config blockquoteLineHeight]];
75
+
76
+ // Re-apply nested blockquote styles to restore their correct indentation
77
+ // (applyBaseBlockquoteStyle overwrites nested indents with the parent's indent)
78
+ [self reapplyNestedStyles:output nestedInfo:nestedInfo levelSpacing:levelSpacing];
79
+
80
+ if (currentDepth == 0) {
81
+ applyBlockSpacingAfter(output, [_config blockquoteMarginBottom]);
82
+ }
83
+ }
84
+
85
+ #pragma mark - Nested Blockquote Handling
86
+
87
+ - (NSArray<NSDictionary *> *)collectNestedBlockquotes:(NSMutableAttributedString *)output
88
+ range:(NSRange)blockquoteRange
89
+ depth:(NSInteger)currentDepth
90
+ {
91
+ NSMutableArray<NSDictionary *> *nestedInfo = [NSMutableArray array];
92
+
93
+ [output
94
+ enumerateAttribute:BlockquoteDepthAttributeName
95
+ inRange:blockquoteRange
96
+ options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
97
+ usingBlock:^(id value, NSRange range, BOOL *stop) {
98
+ NSInteger depth = [value integerValue];
99
+ if (value && depth > currentDepth) {
100
+ [nestedInfo
101
+ addObject:@{kNestedInfoDepthKey : value, kNestedInfoRangeKey : [NSValue valueWithRange:range]}];
102
+ }
103
+ }];
104
+
105
+ return nestedInfo;
106
+ }
107
+
108
+ - (void)applyBaseBlockquoteStyle:(NSMutableAttributedString *)output
109
+ range:(NSRange)blockquoteRange
110
+ depth:(NSInteger)currentDepth
111
+ levelSpacing:(CGFloat)levelSpacing
112
+ backgroundColor:(UIColor *)backgroundColor
113
+ lineHeight:(CGFloat)lineHeight
114
+ {
115
+ NSMutableParagraphStyle *paragraphStyle = getOrCreateParagraphStyle(output, blockquoteRange.location);
116
+ CGFloat totalIndent = [self calculateIndentForDepth:currentDepth levelSpacing:levelSpacing];
117
+ paragraphStyle.firstLineHeadIndent = totalIndent;
118
+ paragraphStyle.headIndent = totalIndent;
119
+
120
+ NSMutableDictionary *newAttributes =
121
+ [NSMutableDictionary dictionaryWithObjectsAndKeys:paragraphStyle, NSParagraphStyleAttributeName, @(currentDepth),
122
+ BlockquoteDepthAttributeName, nil];
123
+ if (backgroundColor) {
124
+ newAttributes[BlockquoteBackgroundColorAttributeName] = backgroundColor;
125
+ }
126
+ [output addAttributes:newAttributes range:blockquoteRange];
127
+
128
+ applyLineHeight(output, blockquoteRange, lineHeight);
129
+ }
130
+
131
+ - (void)reapplyNestedStyles:(NSMutableAttributedString *)output
132
+ nestedInfo:(NSArray<NSDictionary *> *)nestedInfo
133
+ levelSpacing:(CGFloat)levelSpacing
134
+ {
135
+ // Re-apply indentation to nested blockquotes since applyBaseBlockquoteStyle
136
+ // overwrote them with the parent's indentation
137
+ for (NSDictionary *info in nestedInfo) {
138
+ NSRange nestedRange = [info[kNestedInfoRangeKey] rangeValue];
139
+ NSInteger nestedDepth = [info[kNestedInfoDepthKey] integerValue];
140
+ NSMutableParagraphStyle *style = getOrCreateParagraphStyle(output, nestedRange.location);
141
+
142
+ CGFloat indent = [self calculateIndentForDepth:nestedDepth levelSpacing:levelSpacing];
143
+ style.firstLineHeadIndent = indent;
144
+ style.headIndent = indent;
145
+ style.tailIndent = 0;
146
+
147
+ [output
148
+ addAttributes:@{NSParagraphStyleAttributeName : style, BlockquoteDepthAttributeName : info[kNestedInfoDepthKey]}
149
+ range:nestedRange];
150
+ }
151
+ }
152
+
153
+ #pragma mark - Helper Methods
154
+
155
+ - (CGFloat)calculateIndentForDepth:(NSInteger)depth levelSpacing:(CGFloat)levelSpacing
156
+ {
157
+ return (depth + 1) * levelSpacing;
158
+ }
159
+
160
+ @end
@@ -0,0 +1,10 @@
1
+ #pragma once
2
+ #import "NodeRenderer.h"
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ @interface CodeBlockRenderer : NSObject <NodeRenderer>
7
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config;
8
+ @end
9
+
10
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,90 @@
1
+ #import "CodeBlockRenderer.h"
2
+ #import "CodeBlockBackground.h"
3
+ #import "LastElementUtils.h"
4
+ #import "MarkdownASTNode.h"
5
+ #import "ParagraphStyleUtils.h"
6
+ #import "RenderContext.h"
7
+ #import "RendererFactory.h"
8
+ #import "StyleConfig.h"
9
+
10
+ @implementation CodeBlockRenderer {
11
+ RendererFactory *_rendererFactory;
12
+ StyleConfig *_config;
13
+ }
14
+
15
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
16
+ {
17
+ if (self = [super init]) {
18
+ _rendererFactory = rendererFactory;
19
+ _config = (StyleConfig *)config;
20
+ }
21
+ return self;
22
+ }
23
+
24
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
25
+ {
26
+ [context setBlockStyle:BlockTypeCodeBlock font:[_config codeBlockFont] color:[_config codeBlockColor] headingLevel:0];
27
+
28
+ CGFloat padding = [_config codeBlockPadding];
29
+ CGFloat lineHeight = [_config codeBlockLineHeight];
30
+ CGFloat marginTop = [_config codeBlockMarginTop];
31
+ CGFloat marginBottom = [_config codeBlockMarginBottom];
32
+
33
+ NSUInteger blockStart = output.length;
34
+ blockStart += applyBlockSpacingBefore(output, blockStart, marginTop);
35
+
36
+ // Top Padding: Inserted as a spacer character inside the background area
37
+ [output appendAttributedString:kNewlineAttributedString];
38
+ NSMutableParagraphStyle *topSpacerStyle = [context spacerStyleWithHeight:padding spacing:0];
39
+ [output addAttribute:NSParagraphStyleAttributeName value:topSpacerStyle range:NSMakeRange(blockStart, 1)];
40
+
41
+ NSUInteger contentStart = output.length;
42
+ @try {
43
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
44
+ } @finally {
45
+ [context clearBlockStyle];
46
+ }
47
+
48
+ NSUInteger contentEnd = output.length;
49
+ if (contentEnd <= contentStart)
50
+ return;
51
+
52
+ NSRange contentRange = NSMakeRange(contentStart, contentEnd - contentStart);
53
+
54
+ UIFont *codeFont = [_config codeBlockFont];
55
+ UIColor *codeColor = [_config codeBlockColor];
56
+ if (codeColor) {
57
+ [output addAttributes:@{NSFontAttributeName : codeFont, NSForegroundColorAttributeName : codeColor}
58
+ range:contentRange];
59
+ } else {
60
+ [output addAttribute:NSFontAttributeName value:codeFont range:contentRange];
61
+ }
62
+
63
+ if (lineHeight > 0) {
64
+ applyLineHeight(output, contentRange, lineHeight);
65
+ }
66
+
67
+ // Apply horizontal indents to the content
68
+ NSMutableParagraphStyle *baseStyle = [getOrCreateParagraphStyle(output, contentStart) mutableCopy];
69
+ baseStyle.firstLineHeadIndent = padding;
70
+ baseStyle.headIndent = padding;
71
+ baseStyle.tailIndent = -padding;
72
+ [output addAttribute:NSParagraphStyleAttributeName value:baseStyle range:contentRange];
73
+
74
+ // Bottom Padding: Inserted as a spacer character inside the background area
75
+ NSUInteger bottomPaddingStart = output.length;
76
+ [output appendAttributedString:kNewlineAttributedString];
77
+ NSMutableParagraphStyle *bottomPaddingStyle = [context spacerStyleWithHeight:padding spacing:0];
78
+ [output addAttribute:NSParagraphStyleAttributeName value:bottomPaddingStyle range:NSMakeRange(bottomPaddingStart, 1)];
79
+
80
+ // Define the range for background rendering (includes padding, excludes margins)
81
+ NSRange backgroundRange = NSMakeRange(blockStart, output.length - blockStart);
82
+ [output addAttribute:CodeBlockAttributeName value:@YES range:backgroundRange];
83
+
84
+ // External Margin: Applied outside the background range
85
+ if (marginBottom > 0) {
86
+ applyBlockSpacingAfter(output, marginBottom);
87
+ }
88
+ }
89
+
90
+ @end
@@ -0,0 +1,10 @@
1
+ #pragma once
2
+ #import "NodeRenderer.h"
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ @interface CodeRenderer : NSObject <NodeRenderer>
7
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config;
8
+ @end
9
+
10
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,60 @@
1
+ #import "CodeRenderer.h"
2
+ #import "CodeBackground.h"
3
+ #import "FontUtils.h"
4
+ #import "MarkdownASTNode.h"
5
+ #import "RenderContext.h"
6
+ #import "RendererFactory.h"
7
+ #import "StyleConfig.h"
8
+
9
+ @implementation CodeRenderer {
10
+ RendererFactory *_rendererFactory;
11
+ StyleConfig *_config;
12
+ }
13
+
14
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
15
+ {
16
+ self = [super init];
17
+ if (self) {
18
+ _rendererFactory = rendererFactory;
19
+ _config = (StyleConfig *)config;
20
+ }
21
+ return self;
22
+ }
23
+
24
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
25
+ {
26
+
27
+ BlockStyle *blockStyle = [context getBlockStyle];
28
+
29
+ UIColor *codeColor = _config.codeColor;
30
+
31
+ UIFont *blockFont = cachedFontFromBlockStyle(blockStyle, context);
32
+
33
+ UIFontDescriptorSymbolicTraits traits = blockFont.fontDescriptor.symbolicTraits;
34
+ UIFontWeight weight = (traits & UIFontDescriptorTraitBold) ? UIFontWeightBold : UIFontWeightRegular;
35
+
36
+ UIFont *monospacedFont = [UIFont monospacedSystemFontOfSize:blockStyle.fontSize weight:weight];
37
+
38
+ NSUInteger start = output.length;
39
+
40
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
41
+
42
+ NSRange range = [RenderContext rangeForRenderedContent:output start:start];
43
+ if (range.length > 0) {
44
+ NSDictionary *existingAttributes = [output attributesAtIndex:start effectiveRange:NULL];
45
+ NSMutableDictionary *codeAttributes = [existingAttributes ?: @{} mutableCopy];
46
+
47
+ codeAttributes[NSFontAttributeName] = monospacedFont;
48
+ if (codeColor) {
49
+ codeAttributes[NSForegroundColorAttributeName] = codeColor;
50
+ }
51
+ codeAttributes[CodeAttributeName] = @YES;
52
+
53
+ // Store block line height directly for CodeBackground to use
54
+ codeAttributes[@"BlockLineHeight"] = @(blockFont.lineHeight);
55
+
56
+ [output setAttributes:codeAttributes range:range];
57
+ }
58
+ }
59
+
60
+ @end
@@ -0,0 +1,6 @@
1
+ #pragma once
2
+ #import "NodeRenderer.h"
3
+
4
+ @interface EmphasisRenderer : NSObject <NodeRenderer>
5
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config;
6
+ @end
@@ -0,0 +1,96 @@
1
+ #import "EmphasisRenderer.h"
2
+ #import "FontUtils.h"
3
+ #import "MarkdownASTNode.h"
4
+ #import "RenderContext.h"
5
+ #import "RendererFactory.h"
6
+ #import "StyleConfig.h"
7
+
8
+ @implementation EmphasisRenderer {
9
+ RendererFactory *_rendererFactory;
10
+ StyleConfig *_config;
11
+ }
12
+
13
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
14
+ {
15
+ self = [super init];
16
+ if (self) {
17
+ _rendererFactory = rendererFactory;
18
+ _config = (StyleConfig *)config;
19
+ }
20
+ return self;
21
+ }
22
+
23
+ #pragma mark - Rendering
24
+
25
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
26
+ {
27
+ NSUInteger start = output.length;
28
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
29
+
30
+ NSRange range = NSMakeRange(start, output.length - start);
31
+ if (range.length == 0)
32
+ return;
33
+
34
+ BlockStyle *blockStyle = [context getBlockStyle];
35
+ UIColor *configEmphasisColor = [_config emphasisColor];
36
+
37
+ // Cache the Strong color calculation to efficiently detect nested Strong nodes
38
+ UIColor *strongColorToPreserve = [_config strongColor] ? [RenderContext calculateStrongColor:[_config strongColor]
39
+ blockColor:blockStyle.color]
40
+ : nil;
41
+
42
+ [output
43
+ enumerateAttributesInRange:range
44
+ options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
45
+ usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange subrange, BOOL *stop) {
46
+ // 1. Font Optimization: Only apply italic trait if not already present
47
+ UIFont *currentFont =
48
+ attrs[NSFontAttributeName] ?: cachedFontFromBlockStyle(blockStyle, context);
49
+ if (currentFont && !(currentFont.fontDescriptor.symbolicTraits & UIFontDescriptorTraitItalic)) {
50
+ UIFont *italicFont = [self ensureFontIsItalic:currentFont];
51
+ if (italicFont) {
52
+ [output addAttribute:NSFontAttributeName value:italicFont range:subrange];
53
+ }
54
+ }
55
+
56
+ // 2. Color Optimization: Handle nesting and avoid redundant updates
57
+ if (configEmphasisColor) {
58
+ UIColor *currentColor = attrs[NSForegroundColorAttributeName];
59
+ BOOL isLink = attrs[NSLinkAttributeName] != nil;
60
+
61
+ // Verify if the current color belongs to a Strong parent
62
+ BOOL isStrongColor = strongColorToPreserve && [currentColor isEqual:strongColorToPreserve];
63
+
64
+ // Preserving colors for higher-priority elements (links, strong nodes, etc.)
65
+ if (!isLink && !isStrongColor && ![RenderContext shouldPreserveColors:attrs]) {
66
+ // Only modify the string if the color is actually different
67
+ if (![currentColor isEqual:configEmphasisColor]) {
68
+ [output addAttribute:NSForegroundColorAttributeName
69
+ value:configEmphasisColor
70
+ range:subrange];
71
+ }
72
+ }
73
+ }
74
+ }];
75
+ }
76
+
77
+ #pragma mark - Helper Methods
78
+
79
+ - (UIFont *)ensureFontIsItalic:(UIFont *)font
80
+ {
81
+ if (!font)
82
+ return nil;
83
+
84
+ UIFontDescriptorSymbolicTraits traits = font.fontDescriptor.symbolicTraits;
85
+ if (traits & UIFontDescriptorTraitItalic)
86
+ return font;
87
+
88
+ // Combine italic with existing traits (e.g., preserving Bold if present)
89
+ UIFontDescriptorSymbolicTraits combinedTraits = traits | UIFontDescriptorTraitItalic;
90
+ UIFontDescriptor *italicDescriptor = [font.fontDescriptor fontDescriptorWithSymbolicTraits:combinedTraits];
91
+
92
+ // Size 0 in fontWithDescriptor:size: maintains the current point size
93
+ return [UIFont fontWithDescriptor:italicDescriptor size:0] ?: font;
94
+ }
95
+
96
+ @end
@@ -0,0 +1,7 @@
1
+ #import "MarkdownASTNode.h"
2
+ #import "NodeRenderer.h"
3
+ #import "RenderContext.h"
4
+
5
+ @interface HeadingRenderer : NSObject <NodeRenderer>
6
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config;
7
+ @end