react-native-enriched-markdown 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +479 -0
  3. package/ReactNativeEnrichedMarkdown.podspec +27 -0
  4. package/android/build.gradle +101 -0
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +39 -0
  6. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +21 -0
  7. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
  8. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
  9. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
  10. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +25 -0
  11. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +57 -0
  12. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +1164 -0
  13. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
  14. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
  15. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.cpp +16 -0
  16. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.h +20 -0
  17. package/android/gradle.properties +5 -0
  18. package/android/src/main/AndroidManifest.xml +2 -0
  19. package/android/src/main/baseline-prof.txt +65 -0
  20. package/android/src/main/cpp/jni-adapter.cpp +203 -0
  21. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +153 -0
  22. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +30 -0
  23. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +119 -0
  24. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextPackage.kt +17 -0
  25. package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +165 -0
  26. package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkPressEvent.kt +23 -0
  27. package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +29 -0
  28. package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +48 -0
  29. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockStyleContext.kt +166 -0
  30. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +89 -0
  31. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +105 -0
  32. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +35 -0
  33. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +15 -0
  34. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +26 -0
  35. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +54 -0
  36. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +52 -0
  37. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +15 -0
  38. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +28 -0
  39. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListContextManager.kt +105 -0
  40. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +58 -0
  41. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +69 -0
  42. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +99 -0
  43. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +66 -0
  44. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +95 -0
  45. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +85 -0
  46. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrongRenderer.kt +26 -0
  47. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +29 -0
  48. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +44 -0
  49. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BaseListSpan.kt +136 -0
  50. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +135 -0
  51. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +180 -0
  52. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBlockSpan.kt +196 -0
  53. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeSpan.kt +27 -0
  54. package/android/src/main/java/com/swmansion/enriched/markdown/spans/EmphasisSpan.kt +34 -0
  55. package/android/src/main/java/com/swmansion/enriched/markdown/spans/HeadingSpan.kt +38 -0
  56. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +320 -0
  57. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +36 -0
  58. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +37 -0
  59. package/android/src/main/java/com/swmansion/enriched/markdown/spans/MarginBottomSpan.kt +76 -0
  60. package/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt +87 -0
  61. package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrongSpan.kt +37 -0
  62. package/android/src/main/java/com/swmansion/enriched/markdown/spans/TextSpan.kt +26 -0
  63. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ThematicBreakSpan.kt +69 -0
  64. package/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt +69 -0
  65. package/android/src/main/java/com/swmansion/enriched/markdown/styles/BaseBlockStyle.kt +10 -0
  66. package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +48 -0
  67. package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +51 -0
  68. package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeStyle.kt +21 -0
  69. package/android/src/main/java/com/swmansion/enriched/markdown/styles/EmphasisStyle.kt +17 -0
  70. package/android/src/main/java/com/swmansion/enriched/markdown/styles/HeadingStyle.kt +29 -0
  71. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +21 -0
  72. package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineImageStyle.kt +17 -0
  73. package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +19 -0
  74. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +54 -0
  75. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +29 -0
  76. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrongStyle.kt +17 -0
  77. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +180 -0
  78. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +75 -0
  79. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ThematicBreakStyle.kt +23 -0
  80. package/android/src/main/java/com/swmansion/enriched/markdown/utils/AsyncDrawable.kt +91 -0
  81. package/android/src/main/java/com/swmansion/enriched/markdown/utils/HTMLGenerator.kt +809 -0
  82. package/android/src/main/java/com/swmansion/enriched/markdown/utils/MarkdownExtractor.kt +365 -0
  83. package/android/src/main/java/com/swmansion/enriched/markdown/utils/SelectionActionMode.kt +139 -0
  84. package/android/src/main/java/com/swmansion/enriched/markdown/utils/Utils.kt +181 -0
  85. package/android/src/main/jni/CMakeLists.txt +82 -0
  86. package/android/src/main/jni/EnrichedMarkdownTextSpec.cpp +21 -0
  87. package/android/src/main/jni/EnrichedMarkdownTextSpec.h +25 -0
  88. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextComponentDescriptor.h +29 -0
  89. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.cpp +45 -0
  90. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.h +21 -0
  91. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +33 -0
  92. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +49 -0
  93. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +9 -0
  94. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +25 -0
  95. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +19 -0
  96. package/cpp/md4c/md4c.c +6492 -0
  97. package/cpp/md4c/md4c.h +402 -0
  98. package/cpp/parser/MD4CParser.cpp +314 -0
  99. package/cpp/parser/MD4CParser.hpp +23 -0
  100. package/cpp/parser/MarkdownASTNode.hpp +49 -0
  101. package/ios/EnrichedMarkdownText.h +18 -0
  102. package/ios/EnrichedMarkdownText.mm +1074 -0
  103. package/ios/attachments/ImageAttachment.h +23 -0
  104. package/ios/attachments/ImageAttachment.m +185 -0
  105. package/ios/attachments/ThematicBreakAttachment.h +15 -0
  106. package/ios/attachments/ThematicBreakAttachment.m +33 -0
  107. package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
  108. package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
  109. package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
  110. package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +25 -0
  111. package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +57 -0
  112. package/ios/generated/EnrichedMarkdownTextSpec/Props.h +1164 -0
  113. package/ios/generated/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +20 -0
  114. package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
  115. package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
  116. package/ios/generated/EnrichedMarkdownTextSpec/States.cpp +16 -0
  117. package/ios/generated/EnrichedMarkdownTextSpec/States.h +20 -0
  118. package/ios/internals/EnrichedMarkdownTextComponentDescriptor.h +19 -0
  119. package/ios/internals/EnrichedMarkdownTextShadowNode.h +43 -0
  120. package/ios/internals/EnrichedMarkdownTextShadowNode.mm +85 -0
  121. package/ios/internals/EnrichedMarkdownTextState.h +24 -0
  122. package/ios/parser/MarkdownASTNode.h +33 -0
  123. package/ios/parser/MarkdownASTNode.m +32 -0
  124. package/ios/parser/MarkdownParser.h +8 -0
  125. package/ios/parser/MarkdownParser.mm +13 -0
  126. package/ios/parser/MarkdownParserBridge.mm +110 -0
  127. package/ios/renderer/AttributedRenderer.h +9 -0
  128. package/ios/renderer/AttributedRenderer.m +119 -0
  129. package/ios/renderer/BlockquoteRenderer.h +7 -0
  130. package/ios/renderer/BlockquoteRenderer.m +159 -0
  131. package/ios/renderer/CodeBlockRenderer.h +10 -0
  132. package/ios/renderer/CodeBlockRenderer.m +89 -0
  133. package/ios/renderer/CodeRenderer.h +10 -0
  134. package/ios/renderer/CodeRenderer.m +60 -0
  135. package/ios/renderer/EmphasisRenderer.h +6 -0
  136. package/ios/renderer/EmphasisRenderer.m +96 -0
  137. package/ios/renderer/HeadingRenderer.h +7 -0
  138. package/ios/renderer/HeadingRenderer.m +98 -0
  139. package/ios/renderer/ImageRenderer.h +12 -0
  140. package/ios/renderer/ImageRenderer.m +62 -0
  141. package/ios/renderer/LinkRenderer.h +7 -0
  142. package/ios/renderer/LinkRenderer.m +69 -0
  143. package/ios/renderer/ListItemRenderer.h +16 -0
  144. package/ios/renderer/ListItemRenderer.m +91 -0
  145. package/ios/renderer/ListRenderer.h +13 -0
  146. package/ios/renderer/ListRenderer.m +67 -0
  147. package/ios/renderer/NodeRenderer.h +8 -0
  148. package/ios/renderer/ParagraphRenderer.h +7 -0
  149. package/ios/renderer/ParagraphRenderer.m +69 -0
  150. package/ios/renderer/RenderContext.h +88 -0
  151. package/ios/renderer/RenderContext.m +248 -0
  152. package/ios/renderer/RendererFactory.h +12 -0
  153. package/ios/renderer/RendererFactory.m +110 -0
  154. package/ios/renderer/StrongRenderer.h +6 -0
  155. package/ios/renderer/StrongRenderer.m +83 -0
  156. package/ios/renderer/TextRenderer.h +6 -0
  157. package/ios/renderer/TextRenderer.m +16 -0
  158. package/ios/renderer/ThematicBreakRenderer.h +5 -0
  159. package/ios/renderer/ThematicBreakRenderer.m +53 -0
  160. package/ios/styles/StyleConfig.h +228 -0
  161. package/ios/styles/StyleConfig.mm +1467 -0
  162. package/ios/utils/BlockquoteBorder.h +20 -0
  163. package/ios/utils/BlockquoteBorder.m +92 -0
  164. package/ios/utils/CodeBackground.h +19 -0
  165. package/ios/utils/CodeBackground.m +191 -0
  166. package/ios/utils/CodeBlockBackground.h +17 -0
  167. package/ios/utils/CodeBlockBackground.m +87 -0
  168. package/ios/utils/EditMenuUtils.h +22 -0
  169. package/ios/utils/EditMenuUtils.m +118 -0
  170. package/ios/utils/FontUtils.h +20 -0
  171. package/ios/utils/FontUtils.m +13 -0
  172. package/ios/utils/HTMLGenerator.h +20 -0
  173. package/ios/utils/HTMLGenerator.m +779 -0
  174. package/ios/utils/LastElementUtils.h +53 -0
  175. package/ios/utils/ListMarkerDrawer.h +15 -0
  176. package/ios/utils/ListMarkerDrawer.m +127 -0
  177. package/ios/utils/MarkdownExtractor.h +17 -0
  178. package/ios/utils/MarkdownExtractor.m +295 -0
  179. package/ios/utils/ParagraphStyleUtils.h +13 -0
  180. package/ios/utils/ParagraphStyleUtils.m +56 -0
  181. package/ios/utils/PasteboardUtils.h +36 -0
  182. package/ios/utils/PasteboardUtils.m +134 -0
  183. package/ios/utils/RTFExportUtils.h +24 -0
  184. package/ios/utils/RTFExportUtils.m +297 -0
  185. package/ios/utils/RuntimeKeys.h +38 -0
  186. package/ios/utils/RuntimeKeys.m +11 -0
  187. package/ios/utils/TextViewLayoutManager.h +14 -0
  188. package/ios/utils/TextViewLayoutManager.mm +113 -0
  189. package/lib/module/EnrichedMarkdownText.js +34 -0
  190. package/lib/module/EnrichedMarkdownText.js.map +1 -0
  191. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +130 -0
  192. package/lib/module/index.js +5 -0
  193. package/lib/module/index.js.map +1 -0
  194. package/lib/module/normalizeMarkdownStyle.js +340 -0
  195. package/lib/module/normalizeMarkdownStyle.js.map +1 -0
  196. package/lib/module/package.json +1 -0
  197. package/lib/typescript/package.json +1 -0
  198. package/lib/typescript/src/EnrichedMarkdownText.d.ts +101 -0
  199. package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -0
  200. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +111 -0
  201. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -0
  202. package/lib/typescript/src/index.d.ts +5 -0
  203. package/lib/typescript/src/index.d.ts.map +1 -0
  204. package/lib/typescript/src/normalizeMarkdownStyle.d.ts +6 -0
  205. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -0
  206. package/package.json +186 -1
  207. package/react-native.config.js +13 -0
  208. package/src/EnrichedMarkdownText.tsx +152 -0
  209. package/src/EnrichedMarkdownTextNativeComponent.ts +130 -0
  210. package/src/index.tsx +7 -0
  211. package/src/normalizeMarkdownStyle.ts +377 -0
@@ -0,0 +1,69 @@
1
+ #import "LinkRenderer.h"
2
+ #import "FontUtils.h"
3
+ #import "RenderContext.h"
4
+ #import "RendererFactory.h"
5
+ #import "StyleConfig.h"
6
+
7
+ @implementation LinkRenderer {
8
+ RendererFactory *_rendererFactory;
9
+ StyleConfig *_config;
10
+ }
11
+
12
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
13
+ {
14
+ self = [super init];
15
+ if (self) {
16
+ _rendererFactory = rendererFactory;
17
+ _config = (StyleConfig *)config;
18
+ }
19
+ return self;
20
+ }
21
+
22
+ #pragma mark - Rendering
23
+
24
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
25
+ {
26
+ NSUInteger start = output.length;
27
+
28
+ // 1. Render children first to establish base attributes
29
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
30
+
31
+ NSRange range = NSMakeRange(start, output.length - start);
32
+ if (range.length == 0)
33
+ return;
34
+
35
+ // 2. Extract configuration
36
+ NSString *url = node.attributes[@"url"] ?: @"";
37
+ UIColor *linkColor = [_config linkColor];
38
+ NSNumber *underlineStyle = @([_config linkUnderline] ? NSUnderlineStyleSingle : NSUnderlineStyleNone);
39
+
40
+ // 3. Apply core link functionality (non-destructive)
41
+ [output addAttribute:NSLinkAttributeName value:url range:range];
42
+
43
+ // 4. Optimize visual attributes via enumeration to avoid redundant updates
44
+ [output enumerateAttributesInRange:range
45
+ options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
46
+ usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange subrange, BOOL *stop) {
47
+ NSMutableDictionary *newAttributes = [NSMutableDictionary dictionary];
48
+
49
+ // Only apply link color if the subrange isn't already colored by the link style
50
+ if (linkColor && ![attrs[NSForegroundColorAttributeName] isEqual:linkColor]) {
51
+ newAttributes[NSForegroundColorAttributeName] = linkColor;
52
+ newAttributes[NSUnderlineColorAttributeName] = linkColor;
53
+ }
54
+
55
+ // Only update underline style if it differs from the config
56
+ if (![attrs[NSUnderlineStyleAttributeName] isEqual:underlineStyle]) {
57
+ newAttributes[NSUnderlineStyleAttributeName] = underlineStyle;
58
+ }
59
+
60
+ if (newAttributes.count > 0) {
61
+ [output addAttributes:newAttributes range:subrange];
62
+ }
63
+ }];
64
+
65
+ // 5. Register for touch handling
66
+ [context registerLinkRange:range url:url];
67
+ }
68
+
69
+ @end
@@ -0,0 +1,16 @@
1
+ #import "NodeRenderer.h"
2
+
3
+ @class RendererFactory;
4
+ @class StyleConfig;
5
+ @class RenderContext;
6
+
7
+ // Attribute names for list styling
8
+ extern NSString *const ListDepthAttribute;
9
+ extern NSString *const ListTypeAttribute;
10
+ extern NSString *const ListItemNumberAttribute;
11
+
12
+ @interface ListItemRenderer : NSObject <NodeRenderer>
13
+
14
+ - (instancetype)initWithRendererFactory:(RendererFactory *)rendererFactory config:(StyleConfig *)config;
15
+
16
+ @end
@@ -0,0 +1,91 @@
1
+ #import "ListItemRenderer.h"
2
+ #import "MarkdownASTNode.h"
3
+ #import "ParagraphStyleUtils.h"
4
+ #import "RenderContext.h"
5
+ #import "RendererFactory.h"
6
+ #import "StyleConfig.h"
7
+
8
+ NSString *const ListDepthAttribute = @"ListDepth";
9
+ NSString *const ListTypeAttribute = @"ListType";
10
+ NSString *const ListItemNumberAttribute = @"ListItemNumber";
11
+
12
+ @implementation ListItemRenderer {
13
+ RendererFactory *_rendererFactory;
14
+ StyleConfig *_config;
15
+ }
16
+
17
+ - (instancetype)initWithRendererFactory:(RendererFactory *)factory config:(StyleConfig *)config
18
+ {
19
+ if (self = [super init]) {
20
+ _rendererFactory = factory;
21
+ _config = config;
22
+ }
23
+ return self;
24
+ }
25
+
26
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
27
+ {
28
+ if (!context)
29
+ return;
30
+
31
+ // 1. Maintain the ordered list counter
32
+ if (context.listType == ListTypeOrdered) {
33
+ context.listItemNumber++;
34
+ }
35
+
36
+ NSUInteger start = output.length;
37
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
38
+
39
+ // 2. Structural Fix: Ensure paragraph isolation to prevent merged lines
40
+ if (output.length > start && ![output.string hasSuffix:@"\n"]) {
41
+ [output appendAttributedString:kNewlineAttributedString];
42
+ }
43
+
44
+ NSRange itemRange = NSMakeRange(start, output.length - start);
45
+ if (itemRange.length == 0)
46
+ return;
47
+
48
+ // 3. Pre-calculate invariant metadata for this item
49
+ NSInteger currentDepth = context.listDepth - 1;
50
+ // Root items: just marker width + gap (flush to left edge)
51
+ // Nested items: add marginLeft for each nesting level
52
+ CGFloat minMarkerWidth = (context.listType == ListTypeOrdered) ? [_config effectiveListMarginLeftForNumber]
53
+ : [_config effectiveListMarginLeftForBullet];
54
+ CGFloat indent = minMarkerWidth + [_config effectiveListGapWidth] + (currentDepth * [_config listStyleMarginLeft]);
55
+ CGFloat configLineHeight = [_config listStyleLineHeight];
56
+
57
+ // Pre-wrap numbers to avoid repeated allocations in the block
58
+ NSNumber *depthVal = @(currentDepth);
59
+ NSNumber *typeVal = @(context.listType);
60
+ NSNumber *numVal = @(context.listItemNumber);
61
+
62
+ // 4. Protected Styling: Use enumerateAttribute to avoid flattening children
63
+ [output enumerateAttribute:ListDepthAttribute
64
+ inRange:itemRange
65
+ options:0
66
+ usingBlock:^(id depth, NSRange range, BOOL *stop) {
67
+ // Skip if this segment belongs to a deeper nested list
68
+ if (depth && [depth integerValue] > currentDepth)
69
+ return;
70
+
71
+ NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
72
+ style.firstLineHeadIndent = indent;
73
+ style.headIndent = indent;
74
+
75
+ UIFont *font = [output attribute:NSFontAttributeName atIndex:range.location effectiveRange:NULL];
76
+ if (configLineHeight > 0 && font) {
77
+ style.lineHeightMultiple = configLineHeight / font.pointSize;
78
+ }
79
+
80
+ // Final attribute application
81
+ [output addAttributes:@{
82
+ NSParagraphStyleAttributeName : style,
83
+ ListDepthAttribute : depthVal,
84
+ ListTypeAttribute : typeVal,
85
+ ListItemNumberAttribute : numVal
86
+ }
87
+ range:range];
88
+ }];
89
+ }
90
+
91
+ @end
@@ -0,0 +1,13 @@
1
+ #import "NodeRenderer.h"
2
+
3
+ @class RendererFactory;
4
+ @class StyleConfig;
5
+ @class RenderContext;
6
+
7
+ @interface ListRenderer : NSObject <NodeRenderer>
8
+
9
+ - (instancetype)initWithRendererFactory:(RendererFactory *)rendererFactory
10
+ config:(StyleConfig *)config
11
+ isOrdered:(BOOL)isOrdered;
12
+
13
+ @end
@@ -0,0 +1,67 @@
1
+ #import "ListRenderer.h"
2
+ #import "BlockquoteRenderer.h"
3
+ #import "MarkdownASTNode.h"
4
+ #import "ParagraphStyleUtils.h"
5
+ #import "RenderContext.h"
6
+ #import "RendererFactory.h"
7
+ #import "StyleConfig.h"
8
+
9
+ @implementation ListRenderer {
10
+ RendererFactory *_rendererFactory;
11
+ StyleConfig *_config;
12
+ BOOL _isOrdered;
13
+ }
14
+
15
+ - (instancetype)initWithRendererFactory:(RendererFactory *)factory config:(StyleConfig *)config isOrdered:(BOOL)ordered
16
+ {
17
+ if (self = [super init]) {
18
+ _rendererFactory = factory;
19
+ _config = config;
20
+ _isOrdered = ordered;
21
+ }
22
+ return self;
23
+ }
24
+
25
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
26
+ {
27
+ if (!context)
28
+ return;
29
+
30
+ // Snapshot parent state
31
+ NSInteger prevDepth = context.listDepth;
32
+ ListType prevType = context.listType;
33
+ NSInteger prevNum = context.listItemNumber;
34
+
35
+ // Configure depth and type
36
+ context.listDepth = prevDepth + 1;
37
+ context.listType = _isOrdered ? ListTypeOrdered : ListTypeUnordered;
38
+ context.listItemNumber = 0;
39
+
40
+ // Ensure isolation for nested lists
41
+ if (prevDepth > 0 && output.length > 0 && ![output.string hasSuffix:@"\n"]) {
42
+ [output appendAttributedString:kNewlineAttributedString];
43
+ }
44
+
45
+ [context setBlockStyle:_isOrdered ? BlockTypeOrderedList : BlockTypeUnorderedList
46
+ font:[_config listStyleFont]
47
+ color:[_config listStyleColor]
48
+ headingLevel:0];
49
+
50
+ @try {
51
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
52
+ } @finally {
53
+ // Restore parent state
54
+ context.listDepth = prevDepth;
55
+ context.listType = prevType;
56
+ context.listItemNumber = prevNum;
57
+ if (prevDepth == 0)
58
+ [context clearBlockStyle];
59
+ }
60
+
61
+ // Final spacing for root container
62
+ if (prevDepth == 0 && [_config listStyleMarginBottom] > 0) {
63
+ applyBlockSpacing(output, [_config listStyleMarginBottom]);
64
+ }
65
+ }
66
+
67
+ @end
@@ -0,0 +1,8 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ @class MarkdownASTNode;
4
+ @class RenderContext;
5
+
6
+ @protocol NodeRenderer <NSObject>
7
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context;
8
+ @end
@@ -0,0 +1,7 @@
1
+ #import "MarkdownASTNode.h"
2
+ #import "NodeRenderer.h"
3
+ #import "RenderContext.h"
4
+
5
+ @interface ParagraphRenderer : NSObject <NodeRenderer>
6
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config;
7
+ @end
@@ -0,0 +1,69 @@
1
+ #import "ParagraphRenderer.h"
2
+ #import "FontUtils.h"
3
+ #import "MarkdownASTNode.h"
4
+ #import "ParagraphStyleUtils.h"
5
+ #import "RendererFactory.h"
6
+ #import "StyleConfig.h"
7
+
8
+ @implementation ParagraphRenderer {
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
+ // 1. Context-Aware Styling
28
+ // We only set the block style if no parent element (like a list or blockquote) has already established one.
29
+ BOOL isTopLevel = (context.currentBlockType == BlockTypeNone);
30
+
31
+ if (isTopLevel) {
32
+ [context setBlockStyle:BlockTypeParagraph font:_config.paragraphFont color:_config.paragraphColor headingLevel:0];
33
+ }
34
+
35
+ NSUInteger start = output.length;
36
+ @try {
37
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
38
+ } @finally {
39
+ // Only clear the style if this paragraph was the one that set it.
40
+ if (isTopLevel) {
41
+ [context clearBlockStyle];
42
+ }
43
+ }
44
+
45
+ // 2. Geometry and Spacing Logic
46
+ if (output.length <= start)
47
+ return;
48
+ NSRange range = NSMakeRange(start, output.length - start);
49
+
50
+ // Check if this paragraph is purely a wrapper for a block image.
51
+ // Images often require different spacing and should not have standard line height applied.
52
+ BOOL isBlockImage = (node.children.count == 1 && ((MarkdownASTNode *)node.children[0]).type == MarkdownNodeTypeImage);
53
+
54
+ // Apply line height only for text paragraphs to avoid unwanted gaps above/below images.
55
+ if (!isBlockImage) {
56
+ applyLineHeight(output, range, _config.paragraphLineHeight);
57
+ }
58
+
59
+ // 3. Margin Application
60
+ // Only top-level paragraphs apply bottom margins; nested paragraphs defer to their parents.
61
+ CGFloat marginBottom = 0;
62
+ if (isTopLevel) {
63
+ marginBottom = isBlockImage ? _config.imageMarginBottom : _config.paragraphMarginBottom;
64
+ }
65
+
66
+ applyParagraphSpacing(output, start, marginBottom);
67
+ }
68
+
69
+ @end
@@ -0,0 +1,88 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <UIKit/UIKit.h>
3
+
4
+ typedef NS_ENUM(NSInteger, BlockType) {
5
+ BlockTypeNone,
6
+ BlockTypeParagraph,
7
+ BlockTypeHeading,
8
+ BlockTypeBlockquote,
9
+ BlockTypeUnorderedList,
10
+ BlockTypeOrderedList,
11
+ BlockTypeCodeBlock
12
+ };
13
+
14
+ typedef NS_ENUM(NSInteger, ListType) { ListTypeUnordered, ListTypeOrdered };
15
+
16
+ @interface BlockStyle : NSObject
17
+ @property (nonatomic, assign) CGFloat fontSize;
18
+ @property (nonatomic, strong) NSString *fontFamily;
19
+ @property (nonatomic, strong) NSString *fontWeight;
20
+ @property (nonatomic, strong) UIColor *color;
21
+ @property (nonatomic, strong) UIFont *cachedFont;
22
+ @property (nonatomic, strong) NSDictionary *cachedTextAttributes;
23
+ @end
24
+
25
+ @interface RenderContext : NSObject
26
+ @property (nonatomic, strong) NSMutableArray<NSValue *> *linkRanges;
27
+ @property (nonatomic, strong) NSMutableArray<NSString *> *linkURLs;
28
+ @property (nonatomic, assign) BlockType currentBlockType;
29
+ @property (nonatomic, strong) BlockStyle *currentBlockStyle;
30
+ @property (nonatomic, assign) NSInteger currentHeadingLevel;
31
+ @property (nonatomic, assign) NSInteger blockquoteDepth;
32
+ @property (nonatomic, assign) NSInteger listDepth;
33
+ @property (nonatomic, assign) ListType listType;
34
+ @property (nonatomic, assign) NSInteger listItemNumber;
35
+
36
+ - (instancetype)init;
37
+ - (void)reset;
38
+
39
+ - (UIFont *)cachedFontForSize:(CGFloat)fontSize family:(NSString *)fontFamily weight:(NSString *)fontWeight;
40
+ - (NSMutableParagraphStyle *)spacerStyleWithHeight:(CGFloat)height spacing:(CGFloat)spacing;
41
+ - (NSMutableParagraphStyle *)blockSpacerStyleWithMargin:(CGFloat)margin;
42
+ - (void)registerLinkRange:(NSRange)range url:(NSString *)url;
43
+ - (void)setBlockStyle:(BlockType)type
44
+ fontSize:(CGFloat)fontSize
45
+ fontFamily:(NSString *)fontFamily
46
+ fontWeight:(NSString *)fontWeight
47
+ color:(UIColor *)color;
48
+ - (void)setBlockStyle:(BlockType)type
49
+ fontSize:(CGFloat)fontSize
50
+ fontFamily:(NSString *)fontFamily
51
+ fontWeight:(NSString *)fontWeight
52
+ color:(UIColor *)color
53
+ headingLevel:(NSInteger)headingLevel;
54
+ - (void)setBlockStyle:(BlockType)type font:(UIFont *)font color:(UIColor *)color headingLevel:(NSInteger)headingLevel;
55
+ - (BlockStyle *)getBlockStyle;
56
+ - (NSDictionary *)getTextAttributes;
57
+ - (void)clearBlockStyle;
58
+
59
+ /**
60
+ * Checks if colors should be preserved based on existing attributes.
61
+ * Returns YES if the text is inside a link or inline code, which means
62
+ * we should preserve their colors instead of applying new colors.
63
+ */
64
+ + (BOOL)shouldPreserveColors:(NSDictionary *)existingAttributes;
65
+
66
+ /**
67
+ * Calculates the color that strong would use based on the configured strong color and block style.
68
+ * Uses strongColor if explicitly set (different from block color), otherwise uses block color.
69
+ */
70
+ + (UIColor *)calculateStrongColor:(UIColor *)configStrongColor blockColor:(UIColor *)blockColor;
71
+
72
+ /**
73
+ * Calculates the range for content rendered between start and current output length.
74
+ * Returns a range with length 0 if no content was rendered.
75
+ */
76
+ + (NSRange)rangeForRenderedContent:(NSMutableAttributedString *)output start:(NSUInteger)start;
77
+
78
+ /**
79
+ * Applies font and color attributes conditionally, only updating if they've changed.
80
+ * Returns YES if any attributes were updated, NO otherwise.
81
+ */
82
+ + (BOOL)applyFontAndColorAttributes:(NSMutableAttributedString *)output
83
+ range:(NSRange)range
84
+ font:(UIFont *)font
85
+ color:(UIColor *)color
86
+ existingAttributes:(NSDictionary *)existingAttributes
87
+ shouldPreserveColors:(BOOL)shouldPreserveColors;
88
+ @end
@@ -0,0 +1,248 @@
1
+ #import "RenderContext.h"
2
+ #import "CodeBackground.h"
3
+ #import <React/RCTFont.h>
4
+
5
+ @implementation BlockStyle
6
+ @end
7
+
8
+ @implementation RenderContext {
9
+ NSMutableDictionary<NSString *, UIFont *> *_fontCache;
10
+ NSParagraphStyle *_baseSpacerTemplate;
11
+ NSParagraphStyle *_baseBlockSpacerTemplate;
12
+ }
13
+
14
+ - (instancetype)init
15
+ {
16
+ if (self = [super init]) {
17
+ _linkRanges = [NSMutableArray array];
18
+ _linkURLs = [NSMutableArray array];
19
+ _fontCache = [NSMutableDictionary dictionary];
20
+ _currentBlockStyle = [[BlockStyle alloc] init];
21
+
22
+ NSMutableParagraphStyle *spacerTemplate = [[NSMutableParagraphStyle alloc] init];
23
+ _baseSpacerTemplate = [spacerTemplate copy];
24
+
25
+ NSMutableParagraphStyle *blockSpacerTemplate = [[NSMutableParagraphStyle alloc] init];
26
+ blockSpacerTemplate.minimumLineHeight = 1;
27
+ blockSpacerTemplate.maximumLineHeight = 1;
28
+ _baseBlockSpacerTemplate = [blockSpacerTemplate copy];
29
+
30
+ [self reset];
31
+ }
32
+ return self;
33
+ }
34
+
35
+ #pragma mark - Font Cache
36
+
37
+ - (UIFont *)cachedFontForSize:(CGFloat)fontSize family:(NSString *)fontFamily weight:(NSString *)fontWeight
38
+ {
39
+ NSString *weightKey = (fontWeight.length > 0) ? fontWeight : @"";
40
+ NSString *key = [NSString stringWithFormat:@"%.1f|%@|%@", fontSize, fontFamily ?: @"", weightKey];
41
+
42
+ UIFont *cached = _fontCache[key];
43
+ if (cached) {
44
+ return cached;
45
+ }
46
+
47
+ NSString *effectiveWeight = (fontWeight.length > 0) ? fontWeight : nil;
48
+
49
+ UIFont *font = [RCTFont updateFont:nil
50
+ withFamily:fontFamily
51
+ size:@(fontSize)
52
+ weight:effectiveWeight
53
+ style:nil
54
+ variant:nil
55
+ scaleMultiplier:1];
56
+
57
+ if (font) {
58
+ _fontCache[key] = font;
59
+ }
60
+ return font;
61
+ }
62
+
63
+ #pragma mark - Paragraph Style Factory
64
+
65
+ - (NSMutableParagraphStyle *)spacerStyleWithHeight:(CGFloat)height spacing:(CGFloat)spacing
66
+ {
67
+ NSMutableParagraphStyle *style = [_baseSpacerTemplate mutableCopy];
68
+ style.minimumLineHeight = height;
69
+ style.maximumLineHeight = height;
70
+ style.paragraphSpacing = spacing;
71
+ return style;
72
+ }
73
+
74
+ - (NSMutableParagraphStyle *)blockSpacerStyleWithMargin:(CGFloat)margin
75
+ {
76
+ NSMutableParagraphStyle *style = [_baseBlockSpacerTemplate mutableCopy];
77
+ style.paragraphSpacing = margin;
78
+ return style;
79
+ }
80
+
81
+ #pragma mark - Link Registry
82
+
83
+ - (void)registerLinkRange:(NSRange)range url:(NSString *)url
84
+ {
85
+ if (range.length == 0)
86
+ return;
87
+ [self.linkRanges addObject:[NSValue valueWithRange:range]];
88
+ [self.linkURLs addObject:url ?: @""];
89
+ }
90
+
91
+ #pragma mark - Block Style Management
92
+
93
+ /**
94
+ * Updates the shared BlockStyle object with new traits.
95
+ * This avoids allocating a new object for every block node in the AST.
96
+ */
97
+ - (void)setBlockStyle:(BlockType)type
98
+ fontSize:(CGFloat)fontSize
99
+ fontFamily:(NSString *)fontFamily
100
+ fontWeight:(NSString *)fontWeight
101
+ color:(UIColor *)color
102
+ headingLevel:(NSInteger)headingLevel
103
+ {
104
+ _currentBlockType = type;
105
+ _currentHeadingLevel = headingLevel;
106
+
107
+ _currentBlockStyle.fontSize = fontSize;
108
+ _currentBlockStyle.fontFamily = fontFamily ?: @"";
109
+ _currentBlockStyle.fontWeight = fontWeight ?: @"normal";
110
+ _currentBlockStyle.color = color ?: [UIColor blackColor];
111
+ }
112
+
113
+ - (void)setBlockStyle:(BlockType)type
114
+ fontSize:(CGFloat)fontSize
115
+ fontFamily:(NSString *)fontFamily
116
+ fontWeight:(NSString *)fontWeight
117
+ color:(UIColor *)color
118
+ {
119
+ [self setBlockStyle:type fontSize:fontSize fontFamily:fontFamily fontWeight:fontWeight color:color headingLevel:0];
120
+ }
121
+
122
+ - (void)setBlockStyle:(BlockType)type font:(UIFont *)font color:(UIColor *)color headingLevel:(NSInteger)headingLevel
123
+ {
124
+ _currentBlockType = type;
125
+ _currentHeadingLevel = headingLevel;
126
+
127
+ UIColor *finalColor = color ?: [UIColor blackColor];
128
+ _currentBlockStyle.cachedFont = font;
129
+ _currentBlockStyle.color = finalColor;
130
+
131
+ // Pre-create text attributes dictionary if we have both font and color
132
+ if (font) {
133
+ _currentBlockStyle.cachedTextAttributes =
134
+ @{NSFontAttributeName : font, NSForegroundColorAttributeName : finalColor};
135
+ }
136
+ }
137
+
138
+ - (BlockStyle *)getBlockStyle
139
+ {
140
+ return _currentBlockStyle;
141
+ }
142
+
143
+ - (NSDictionary *)getTextAttributes
144
+ {
145
+ BlockStyle *style = _currentBlockStyle;
146
+
147
+ // Return pre-cached attributes if available
148
+ if (style.cachedTextAttributes) {
149
+ return style.cachedTextAttributes;
150
+ }
151
+
152
+ // Fall back to creating attributes from font cache
153
+ UIFont *font = style.cachedFont;
154
+ if (!font) {
155
+ font = [self cachedFontForSize:style.fontSize family:style.fontFamily weight:style.fontWeight];
156
+ }
157
+ UIColor *color = style.color ?: [UIColor blackColor];
158
+
159
+ // Cache for future calls within same block
160
+ style.cachedTextAttributes = @{NSFontAttributeName : font, NSForegroundColorAttributeName : color};
161
+ return style.cachedTextAttributes;
162
+ }
163
+
164
+ - (void)clearBlockStyle
165
+ {
166
+ _currentBlockType = BlockTypeNone;
167
+ _currentHeadingLevel = 0;
168
+ _currentBlockStyle.cachedFont = nil;
169
+ _currentBlockStyle.cachedTextAttributes = nil;
170
+ }
171
+
172
+ #pragma mark - Reset
173
+
174
+ - (void)reset
175
+ {
176
+ [_linkRanges removeAllObjects];
177
+ [_linkURLs removeAllObjects];
178
+ [self clearBlockStyle];
179
+
180
+ _blockquoteDepth = 0;
181
+ _listDepth = 0;
182
+ _listType = ListTypeUnordered;
183
+ _listItemNumber = 0;
184
+
185
+ // Revert shared style object to baseline defaults
186
+ _currentBlockStyle.fontSize = 0;
187
+ _currentBlockStyle.fontFamily = @"";
188
+ _currentBlockStyle.fontWeight = @"";
189
+ _currentBlockStyle.color = [UIColor blackColor];
190
+ }
191
+
192
+ #pragma mark - Static Utilities
193
+
194
+ /**
195
+ * Determines if specific inline attributes should protect the current color.
196
+ */
197
+ + (BOOL)shouldPreserveColors:(NSDictionary *)attrs
198
+ {
199
+ return (attrs[NSLinkAttributeName] != nil || attrs[CodeAttributeName] != nil);
200
+ }
201
+
202
+ /**
203
+ * Calculates whether a strong color should override the block color.
204
+ */
205
+ + (UIColor *)calculateStrongColor:(UIColor *)configStrongColor blockColor:(UIColor *)blockColor
206
+ {
207
+ if (!configStrongColor || [configStrongColor isEqual:blockColor]) {
208
+ return blockColor;
209
+ }
210
+ return configStrongColor;
211
+ }
212
+
213
+ /**
214
+ * Safely calculates a range based on a start point and the current output length.
215
+ */
216
+ + (NSRange)rangeForRenderedContent:(NSMutableAttributedString *)output start:(NSUInteger)start
217
+ {
218
+ if (output.length < start)
219
+ return NSMakeRange(start, 0);
220
+ return NSMakeRange(start, output.length - start);
221
+ }
222
+
223
+ /**
224
+ * Surgically applies attributes only if they differ from current values.
225
+ * This minimizes "dirtying" the AttributedString, which improves layout performance.
226
+ */
227
+ + (void)applyFontAndColorAttributes:(NSMutableAttributedString *)output
228
+ range:(NSRange)range
229
+ font:(UIFont *)font
230
+ color:(UIColor *)color
231
+ existingAttributes:(NSDictionary *)attrs
232
+ shouldPreserveColors:(BOOL)shouldPreserve
233
+ {
234
+ if (range.length == 0)
235
+ return;
236
+
237
+ // Font Update: Only if it exists and is different
238
+ if (font && ![font isEqual:attrs[NSFontAttributeName]]) {
239
+ [output addAttribute:NSFontAttributeName value:font range:range];
240
+ }
241
+
242
+ // Color Update: Only if not a link/code and different from existing
243
+ if (color && !shouldPreserve && ![color isEqual:attrs[NSForegroundColorAttributeName]]) {
244
+ [output addAttribute:NSForegroundColorAttributeName value:color range:range];
245
+ }
246
+ }
247
+
248
+ @end
@@ -0,0 +1,12 @@
1
+ #import "MarkdownASTNode.h"
2
+ #import "NodeRenderer.h"
3
+
4
+ @class RenderContext;
5
+
6
+ @interface RendererFactory : NSObject
7
+ - (instancetype)initWithConfig:(id)config;
8
+ - (id<NodeRenderer>)rendererForNodeType:(MarkdownNodeType)type;
9
+ - (void)renderChildrenOfNode:(MarkdownASTNode *)node
10
+ into:(NSMutableAttributedString *)output
11
+ context:(RenderContext *)context;
12
+ @end