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,312 @@
1
+ #import "RenderContext.h"
2
+ #import "CodeBackground.h"
3
+ #import "FontUtils.h"
4
+ #import <React/RCTFont.h>
5
+
6
+ @implementation BlockStyle
7
+ @end
8
+
9
+ @implementation RenderContext {
10
+ NSMutableDictionary<NSString *, UIFont *> *_fontCache;
11
+ NSParagraphStyle *_baseSpacerTemplate;
12
+ NSParagraphStyle *_baseBlockSpacerTemplate;
13
+ }
14
+
15
+ - (instancetype)init
16
+ {
17
+ if (self = [super init]) {
18
+ _linkRanges = [NSMutableArray array];
19
+ _linkURLs = [NSMutableArray array];
20
+ _headingRanges = [NSMutableArray array];
21
+ _headingLevels = [NSMutableArray array];
22
+ _imageRanges = [NSMutableArray array];
23
+ _imageAltTexts = [NSMutableArray array];
24
+ _imageURLs = [NSMutableArray array];
25
+ _listItemRanges = [NSMutableArray array];
26
+ _listItemPositions = [NSMutableArray array];
27
+ _listItemDepths = [NSMutableArray array];
28
+ _listItemOrdered = [NSMutableArray array];
29
+ _fontCache = [NSMutableDictionary dictionary];
30
+ _currentBlockStyle = [[BlockStyle alloc] init];
31
+ _allowFontScaling = YES;
32
+ _maxFontSizeMultiplier = 0;
33
+
34
+ NSMutableParagraphStyle *spacerTemplate = [[NSMutableParagraphStyle alloc] init];
35
+ _baseSpacerTemplate = [spacerTemplate copy];
36
+
37
+ NSMutableParagraphStyle *blockSpacerTemplate = [[NSMutableParagraphStyle alloc] init];
38
+ blockSpacerTemplate.minimumLineHeight = 1;
39
+ blockSpacerTemplate.maximumLineHeight = 1;
40
+ _baseBlockSpacerTemplate = [blockSpacerTemplate copy];
41
+
42
+ [self reset];
43
+ }
44
+ return self;
45
+ }
46
+
47
+ #pragma mark - Font Cache
48
+
49
+ - (UIFont *)cachedFontForSize:(CGFloat)fontSize family:(NSString *)fontFamily weight:(NSString *)fontWeight
50
+ {
51
+ CGFloat effectiveMultiplier = _allowFontScaling ? RCTFontSizeMultiplierWithMax(_maxFontSizeMultiplier) : 1.0;
52
+
53
+ NSString *weightKey = (fontWeight.length > 0) ? fontWeight : @"";
54
+ NSString *key =
55
+ [NSString stringWithFormat:@"%.1f|%@|%@|%.2f", fontSize, fontFamily ?: @"", weightKey, effectiveMultiplier];
56
+
57
+ UIFont *cached = _fontCache[key];
58
+ if (cached) {
59
+ return cached;
60
+ }
61
+
62
+ NSString *effectiveWeight = (fontWeight.length > 0) ? fontWeight : nil;
63
+
64
+ UIFont *font = [RCTFont updateFont:nil
65
+ withFamily:fontFamily
66
+ size:@(fontSize)
67
+ weight:effectiveWeight
68
+ style:nil
69
+ variant:nil
70
+ scaleMultiplier:effectiveMultiplier];
71
+
72
+ if (font) {
73
+ _fontCache[key] = font;
74
+ }
75
+ return font;
76
+ }
77
+
78
+ #pragma mark - Paragraph Style Factory
79
+
80
+ - (NSMutableParagraphStyle *)spacerStyleWithHeight:(CGFloat)height spacing:(CGFloat)spacing
81
+ {
82
+ NSMutableParagraphStyle *style = [_baseSpacerTemplate mutableCopy];
83
+ style.minimumLineHeight = height;
84
+ style.maximumLineHeight = height;
85
+ style.paragraphSpacing = spacing;
86
+ return style;
87
+ }
88
+
89
+ - (NSMutableParagraphStyle *)blockSpacerStyleWithMargin:(CGFloat)margin
90
+ {
91
+ NSMutableParagraphStyle *style = [_baseBlockSpacerTemplate mutableCopy];
92
+ style.paragraphSpacing = margin;
93
+ return style;
94
+ }
95
+
96
+ #pragma mark - Link Registry
97
+
98
+ - (void)registerLinkRange:(NSRange)range url:(NSString *)url
99
+ {
100
+ if (range.length == 0)
101
+ return;
102
+ [self.linkRanges addObject:[NSValue valueWithRange:range]];
103
+ [self.linkURLs addObject:url ?: @""];
104
+ }
105
+
106
+ - (void)registerHeadingRange:(NSRange)range level:(NSInteger)level text:(NSString *)text
107
+ {
108
+ if (range.length == 0)
109
+ return;
110
+ [self.headingRanges addObject:[NSValue valueWithRange:range]];
111
+ [self.headingLevels addObject:@(level)];
112
+ }
113
+
114
+ - (void)registerImageRange:(NSRange)range altText:(NSString *)altText url:(NSString *)url
115
+ {
116
+ if (range.length == 0)
117
+ return;
118
+ [self.imageRanges addObject:[NSValue valueWithRange:range]];
119
+ [self.imageAltTexts addObject:altText ?: @""];
120
+ [self.imageURLs addObject:url ?: @""];
121
+ }
122
+
123
+ #pragma mark - Registration Helpers
124
+
125
+ - (void)registerListItemRange:(NSRange)range
126
+ position:(NSInteger)position
127
+ depth:(NSInteger)depth
128
+ isOrdered:(BOOL)isOrdered
129
+ {
130
+ if (![self isValidRange:range])
131
+ return;
132
+
133
+ [self.listItemRanges addObject:[NSValue valueWithRange:range]];
134
+ [self.listItemPositions addObject:@(position)];
135
+ [self.listItemDepths addObject:@(depth)];
136
+ [self.listItemOrdered addObject:@(isOrdered)];
137
+ }
138
+
139
+ #pragma mark - Private
140
+
141
+ - (BOOL)isValidRange:(NSRange)range
142
+ {
143
+ return range.length > 0 && range.location != NSNotFound;
144
+ }
145
+
146
+ #pragma mark - Block Style Management
147
+
148
+ /**
149
+ * Updates the shared BlockStyle object with new traits.
150
+ * This avoids allocating a new object for every block node in the AST.
151
+ */
152
+ - (void)setBlockStyle:(BlockType)type
153
+ fontSize:(CGFloat)fontSize
154
+ fontFamily:(NSString *)fontFamily
155
+ fontWeight:(NSString *)fontWeight
156
+ color:(UIColor *)color
157
+ headingLevel:(NSInteger)headingLevel
158
+ {
159
+ _currentBlockType = type;
160
+ _currentHeadingLevel = headingLevel;
161
+
162
+ _currentBlockStyle.fontSize = fontSize;
163
+ _currentBlockStyle.fontFamily = fontFamily ?: @"";
164
+ _currentBlockStyle.fontWeight = fontWeight ?: @"normal";
165
+ _currentBlockStyle.color = color ?: [UIColor blackColor];
166
+ }
167
+
168
+ - (void)setBlockStyle:(BlockType)type
169
+ fontSize:(CGFloat)fontSize
170
+ fontFamily:(NSString *)fontFamily
171
+ fontWeight:(NSString *)fontWeight
172
+ color:(UIColor *)color
173
+ {
174
+ [self setBlockStyle:type fontSize:fontSize fontFamily:fontFamily fontWeight:fontWeight color:color headingLevel:0];
175
+ }
176
+
177
+ - (void)setBlockStyle:(BlockType)type font:(UIFont *)font color:(UIColor *)color headingLevel:(NSInteger)headingLevel
178
+ {
179
+ _currentBlockType = type;
180
+ _currentHeadingLevel = headingLevel;
181
+
182
+ UIColor *finalColor = color ?: [UIColor blackColor];
183
+ _currentBlockStyle.cachedFont = font;
184
+ _currentBlockStyle.color = finalColor;
185
+
186
+ // Pre-create text attributes dictionary if we have both font and color
187
+ if (font) {
188
+ _currentBlockStyle.cachedTextAttributes =
189
+ @{NSFontAttributeName : font, NSForegroundColorAttributeName : finalColor};
190
+ }
191
+ }
192
+
193
+ - (BlockStyle *)getBlockStyle
194
+ {
195
+ return _currentBlockStyle;
196
+ }
197
+
198
+ - (NSDictionary *)getTextAttributes
199
+ {
200
+ BlockStyle *style = _currentBlockStyle;
201
+
202
+ // Return pre-cached attributes if available
203
+ if (style.cachedTextAttributes) {
204
+ return style.cachedTextAttributes;
205
+ }
206
+
207
+ // Fall back to creating attributes from font cache
208
+ UIFont *font = style.cachedFont;
209
+ if (!font) {
210
+ font = [self cachedFontForSize:style.fontSize family:style.fontFamily weight:style.fontWeight];
211
+ }
212
+ UIColor *color = style.color ?: [UIColor blackColor];
213
+
214
+ // Cache for future calls within same block
215
+ style.cachedTextAttributes = @{NSFontAttributeName : font, NSForegroundColorAttributeName : color};
216
+ return style.cachedTextAttributes;
217
+ }
218
+
219
+ - (void)clearBlockStyle
220
+ {
221
+ _currentBlockType = BlockTypeNone;
222
+ _currentHeadingLevel = 0;
223
+ _currentBlockStyle.cachedFont = nil;
224
+ _currentBlockStyle.cachedTextAttributes = nil;
225
+ }
226
+
227
+ #pragma mark - Reset
228
+
229
+ - (void)reset
230
+ {
231
+ [_linkRanges removeAllObjects];
232
+ [_linkURLs removeAllObjects];
233
+ [_headingRanges removeAllObjects];
234
+ [_headingLevels removeAllObjects];
235
+ [_imageRanges removeAllObjects];
236
+ [_imageAltTexts removeAllObjects];
237
+ [_imageURLs removeAllObjects];
238
+ [_listItemRanges removeAllObjects];
239
+ [_listItemPositions removeAllObjects];
240
+ [_listItemDepths removeAllObjects];
241
+ [_listItemOrdered removeAllObjects];
242
+ [self clearBlockStyle];
243
+
244
+ _blockquoteDepth = 0;
245
+ _listDepth = 0;
246
+ _listType = ListTypeUnordered;
247
+ _listItemNumber = 0;
248
+
249
+ // Revert shared style object to baseline defaults
250
+ _currentBlockStyle.fontSize = 0;
251
+ _currentBlockStyle.fontFamily = @"";
252
+ _currentBlockStyle.fontWeight = @"";
253
+ _currentBlockStyle.color = [UIColor blackColor];
254
+ }
255
+
256
+ #pragma mark - Static Utilities
257
+
258
+ /**
259
+ * Determines if specific inline attributes should protect the current color.
260
+ */
261
+ + (BOOL)shouldPreserveColors:(NSDictionary *)attrs
262
+ {
263
+ return (attrs[NSLinkAttributeName] != nil || attrs[CodeAttributeName] != nil);
264
+ }
265
+
266
+ /**
267
+ * Calculates whether a strong color should override the block color.
268
+ */
269
+ + (UIColor *)calculateStrongColor:(UIColor *)configStrongColor blockColor:(UIColor *)blockColor
270
+ {
271
+ if (!configStrongColor || [configStrongColor isEqual:blockColor]) {
272
+ return blockColor;
273
+ }
274
+ return configStrongColor;
275
+ }
276
+
277
+ /**
278
+ * Safely calculates a range based on a start point and the current output length.
279
+ */
280
+ + (NSRange)rangeForRenderedContent:(NSMutableAttributedString *)output start:(NSUInteger)start
281
+ {
282
+ if (output.length < start)
283
+ return NSMakeRange(start, 0);
284
+ return NSMakeRange(start, output.length - start);
285
+ }
286
+
287
+ /**
288
+ * Surgically applies attributes only if they differ from current values.
289
+ * This minimizes "dirtying" the AttributedString, which improves layout performance.
290
+ */
291
+ + (void)applyFontAndColorAttributes:(NSMutableAttributedString *)output
292
+ range:(NSRange)range
293
+ font:(UIFont *)font
294
+ color:(UIColor *)color
295
+ existingAttributes:(NSDictionary *)attrs
296
+ shouldPreserveColors:(BOOL)shouldPreserve
297
+ {
298
+ if (range.length == 0)
299
+ return;
300
+
301
+ // Font Update: Only if it exists and is different
302
+ if (font && ![font isEqual:attrs[NSFontAttributeName]]) {
303
+ [output addAttribute:NSFontAttributeName value:font range:range];
304
+ }
305
+
306
+ // Color Update: Only if not a link/code and different from existing
307
+ if (color && !shouldPreserve && ![color isEqual:attrs[NSForegroundColorAttributeName]]) {
308
+ [output addAttribute:NSForegroundColorAttributeName value:color range:range];
309
+ }
310
+ }
311
+
312
+ @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
@@ -0,0 +1,116 @@
1
+ #import "RendererFactory.h"
2
+ #import "BlockquoteRenderer.h"
3
+ #import "CodeBlockRenderer.h"
4
+ #import "CodeRenderer.h"
5
+ #import "EmphasisRenderer.h"
6
+ #import "HeadingRenderer.h"
7
+ #import "ImageRenderer.h"
8
+ #import "LinkRenderer.h"
9
+ #import "ListItemRenderer.h"
10
+ #import "ListRenderer.h"
11
+ #import "ParagraphRenderer.h"
12
+ #import "RenderContext.h"
13
+ #import "StrikethroughRenderer.h"
14
+ #import "StrongRenderer.h"
15
+ #import "StyleConfig.h"
16
+ #import "TextRenderer.h"
17
+ #import "ThematicBreakRenderer.h"
18
+ #import "UnderlineRenderer.h"
19
+
20
+ @implementation RendererFactory {
21
+ StyleConfig *_config;
22
+ NSMutableDictionary<NSNumber *, id<NodeRenderer>> *_cache;
23
+ }
24
+
25
+ /**
26
+ * Initializes the factory with a shared style configuration.
27
+ * Uses a mutable dictionary to cache renderer instances as they are needed.
28
+ */
29
+ - (instancetype)initWithConfig:(StyleConfig *)config
30
+ {
31
+ self = [super init];
32
+ if (self) {
33
+ _config = config;
34
+ _cache = [NSMutableDictionary new];
35
+ }
36
+ return self;
37
+ }
38
+
39
+ /**
40
+ * Returns a shared renderer instance for a specific node type.
41
+ * Implements lazy initialization to avoid allocating unused renderers.
42
+ */
43
+ - (id<NodeRenderer>)rendererForNodeType:(MarkdownNodeType)type
44
+ {
45
+ id<NodeRenderer> cached = _cache[@(type)];
46
+ if (cached) {
47
+ return cached;
48
+ }
49
+
50
+ id<NodeRenderer> renderer = [self createRendererForType:type];
51
+ if (renderer) {
52
+ _cache[@(type)] = renderer;
53
+ }
54
+ return renderer;
55
+ }
56
+
57
+ /**
58
+ * Internal factory method to instantiate specialized renderers.
59
+ */
60
+ - (id<NodeRenderer>)createRendererForType:(MarkdownNodeType)type
61
+ {
62
+ switch (type) {
63
+ case MarkdownNodeTypeText:
64
+ return [TextRenderer new];
65
+ case MarkdownNodeTypeStrong:
66
+ return [[StrongRenderer alloc] initWithRendererFactory:self config:_config];
67
+ case MarkdownNodeTypeEmphasis:
68
+ return [[EmphasisRenderer alloc] initWithRendererFactory:self config:_config];
69
+ case MarkdownNodeTypeStrikethrough:
70
+ return [[StrikethroughRenderer alloc] initWithRendererFactory:self config:_config];
71
+ case MarkdownNodeTypeUnderline:
72
+ return [[UnderlineRenderer alloc] initWithRendererFactory:self config:_config];
73
+ case MarkdownNodeTypeParagraph:
74
+ return [[ParagraphRenderer alloc] initWithRendererFactory:self config:_config];
75
+ case MarkdownNodeTypeLink:
76
+ return [[LinkRenderer alloc] initWithRendererFactory:self config:_config];
77
+ case MarkdownNodeTypeHeading:
78
+ return [[HeadingRenderer alloc] initWithRendererFactory:self config:_config];
79
+ case MarkdownNodeTypeCode:
80
+ return [[CodeRenderer alloc] initWithRendererFactory:self config:_config];
81
+ case MarkdownNodeTypeImage:
82
+ return [[ImageRenderer alloc] initWithRendererFactory:self config:_config];
83
+ case MarkdownNodeTypeBlockquote:
84
+ return [[BlockquoteRenderer alloc] initWithRendererFactory:self config:_config];
85
+ case MarkdownNodeTypeListItem:
86
+ return [[ListItemRenderer alloc] initWithRendererFactory:self config:_config];
87
+ case MarkdownNodeTypeUnorderedList:
88
+ return [[ListRenderer alloc] initWithRendererFactory:self config:_config isOrdered:NO];
89
+ case MarkdownNodeTypeOrderedList:
90
+ return [[ListRenderer alloc] initWithRendererFactory:self config:_config isOrdered:YES];
91
+ case MarkdownNodeTypeCodeBlock:
92
+ return [[CodeBlockRenderer alloc] initWithRendererFactory:self config:_config];
93
+ case MarkdownNodeTypeThematicBreak:
94
+ return [[ThematicBreakRenderer alloc] initWithRendererFactory:self config:_config];
95
+ default:
96
+ return nil;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Helper method for container renderers to process their children.
102
+ * Leverages the factory to find the appropriate renderer for each child node.
103
+ */
104
+ - (void)renderChildrenOfNode:(MarkdownASTNode *)node
105
+ into:(NSMutableAttributedString *)output
106
+ context:(RenderContext *)context
107
+ {
108
+ for (MarkdownASTNode *child in node.children) {
109
+ id<NodeRenderer> renderer = [self rendererForNodeType:child.type];
110
+ if (renderer) {
111
+ [renderer renderNode:child into:output context:context];
112
+ }
113
+ }
114
+ }
115
+
116
+ @end
@@ -0,0 +1,6 @@
1
+ #pragma once
2
+ #import "NodeRenderer.h"
3
+
4
+ @interface StrikethroughRenderer : NSObject <NodeRenderer>
5
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config;
6
+ @end
@@ -0,0 +1,40 @@
1
+ #import "StrikethroughRenderer.h"
2
+ #import "MarkdownASTNode.h"
3
+ #import "RenderContext.h"
4
+ #import "RendererFactory.h"
5
+ #import "StyleConfig.h"
6
+
7
+ @implementation StrikethroughRenderer {
8
+ RendererFactory *_rendererFactory;
9
+ StyleConfig *_config;
10
+ }
11
+
12
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
13
+ {
14
+ if (self = [super init]) {
15
+ _rendererFactory = rendererFactory;
16
+ _config = (StyleConfig *)config;
17
+ }
18
+ return self;
19
+ }
20
+
21
+ #pragma mark - Rendering
22
+
23
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
24
+ {
25
+ NSUInteger start = output.length;
26
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
27
+
28
+ NSRange range = NSMakeRange(start, output.length - start);
29
+ if (range.length == 0)
30
+ return;
31
+
32
+ // Apply strikethrough style
33
+ [output addAttribute:NSStrikethroughStyleAttributeName value:@(NSUnderlineStyleSingle) range:range];
34
+
35
+ // Apply strikethrough line color (not text color)
36
+ UIColor *strikethroughColor = [_config strikethroughColor];
37
+ [output addAttribute:NSStrikethroughColorAttributeName value:strikethroughColor range:range];
38
+ }
39
+
40
+ @end
@@ -0,0 +1,6 @@
1
+ #pragma once
2
+ #import "NodeRenderer.h"
3
+
4
+ @interface StrongRenderer : NSObject <NodeRenderer>
5
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config;
6
+ @end
@@ -0,0 +1,83 @@
1
+ #import "StrongRenderer.h"
2
+ #import "FontUtils.h"
3
+ #import "MarkdownASTNode.h"
4
+ #import "RenderContext.h"
5
+ #import "RendererFactory.h"
6
+ #import "StyleConfig.h"
7
+
8
+ @implementation StrongRenderer {
9
+ RendererFactory *_rendererFactory;
10
+ StyleConfig *_config;
11
+ }
12
+
13
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
14
+ {
15
+ if (self = [super init]) {
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
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
28
+
29
+ NSRange range = NSMakeRange(start, output.length - start);
30
+ if (range.length == 0)
31
+ return;
32
+
33
+ BlockStyle *blockStyle = [context getBlockStyle];
34
+ UIColor *configStrongColor = [_config strongColor];
35
+ UIColor *calculatedColor =
36
+ configStrongColor ? [RenderContext calculateStrongColor:configStrongColor blockColor:blockStyle.color] : nil;
37
+
38
+ [output enumerateAttributesInRange:range
39
+ options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
40
+ usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange subrange, BOOL *stop) {
41
+ // 1. Resolve Font
42
+ UIFont *currentFont =
43
+ attrs[NSFontAttributeName] ?: cachedFontFromBlockStyle(blockStyle, context);
44
+
45
+ // Optimization: Only apply bold if not already bold
46
+ if (!([currentFont.fontDescriptor symbolicTraits] & UIFontDescriptorTraitBold)) {
47
+ UIFont *boldFont = [self ensureFontIsBold:currentFont];
48
+ if (boldFont) {
49
+ [output addAttribute:NSFontAttributeName value:boldFont range:subrange];
50
+ }
51
+ }
52
+
53
+ // 2. Resolve Color
54
+ // Only apply if we have a color and the current segment doesn't explicitly forbid overrides
55
+ if (calculatedColor && ![RenderContext shouldPreserveColors:attrs]) {
56
+ // Optimization: Check if this color is already set to avoid redundant attribute changes
57
+ if (![attrs[NSForegroundColorAttributeName] isEqual:calculatedColor]) {
58
+ [output addAttribute:NSForegroundColorAttributeName
59
+ value:calculatedColor
60
+ range:subrange];
61
+ }
62
+ }
63
+ }];
64
+ }
65
+
66
+ #pragma mark - Helper Methods
67
+
68
+ - (UIFont *)ensureFontIsBold:(UIFont *)font
69
+ {
70
+ if (!font)
71
+ return nil;
72
+
73
+ UIFontDescriptor *descriptor = font.fontDescriptor;
74
+ UIFontDescriptorSymbolicTraits traits = descriptor.symbolicTraits;
75
+
76
+ // Create new descriptor combining current traits with Bold
77
+ UIFontDescriptor *boldDescriptor = [descriptor fontDescriptorWithSymbolicTraits:(traits | UIFontDescriptorTraitBold)];
78
+
79
+ // Fallback to original font if bold version is unavailable
80
+ return boldDescriptor ? [UIFont fontWithDescriptor:boldDescriptor size:0] : font;
81
+ }
82
+
83
+ @end
@@ -0,0 +1,6 @@
1
+ #import "MarkdownASTNode.h"
2
+ #import "NodeRenderer.h"
3
+ #import "RenderContext.h"
4
+
5
+ @interface TextRenderer : NSObject <NodeRenderer>
6
+ @end
@@ -0,0 +1,16 @@
1
+ #import "TextRenderer.h"
2
+ #import "RenderContext.h"
3
+
4
+ @implementation TextRenderer
5
+
6
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
7
+ {
8
+ if (!node.content)
9
+ return;
10
+
11
+ NSAttributedString *text = [[NSAttributedString alloc] initWithString:node.content
12
+ attributes:[context getTextAttributes]];
13
+ [output appendAttributedString:text];
14
+ }
15
+
16
+ @end
@@ -0,0 +1,5 @@
1
+ #import "NodeRenderer.h"
2
+
3
+ @interface ThematicBreakRenderer : NSObject <NodeRenderer>
4
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config;
5
+ @end
@@ -0,0 +1,53 @@
1
+ #import "ThematicBreakRenderer.h"
2
+ #import "MarkdownASTNode.h"
3
+ #import "StyleConfig.h"
4
+ #import "ThematicBreakAttachment.h"
5
+
6
+ #pragma mark - Renderer Implementation
7
+
8
+ @implementation ThematicBreakRenderer {
9
+ __weak id _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
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
24
+ {
25
+ [self ensureStartingNewline:output];
26
+
27
+ ThematicBreakAttachment *attachment = [[ThematicBreakAttachment alloc] init];
28
+ attachment.lineColor = _config.thematicBreakColor ?: [UIColor separatorColor];
29
+ attachment.lineHeight = _config.thematicBreakHeight > 0 ? _config.thematicBreakHeight : 1.0;
30
+ attachment.marginTop = _config.thematicBreakMarginTop;
31
+ attachment.marginBottom = _config.thematicBreakMarginBottom;
32
+
33
+ NSDictionary *attributes = @{
34
+ NSAttachmentAttributeName : attachment,
35
+ NSParagraphStyleAttributeName : [NSParagraphStyle defaultParagraphStyle]
36
+ };
37
+
38
+ NSAttributedString *breakString = [[NSAttributedString alloc] initWithString:@"\uFFFC" attributes:attributes];
39
+
40
+ [output appendAttributedString:breakString];
41
+ [output appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]];
42
+ }
43
+
44
+ #pragma mark - Private Utilities
45
+
46
+ - (void)ensureStartingNewline:(NSMutableAttributedString *)output
47
+ {
48
+ if (output.length > 0 && ![output.string hasSuffix:@"\n"]) {
49
+ [output appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]];
50
+ }
51
+ }
52
+
53
+ @end
@@ -0,0 +1,6 @@
1
+ #import "NodeRenderer.h"
2
+ #import <Foundation/Foundation.h>
3
+
4
+ @interface UnderlineRenderer : NSObject <NodeRenderer>
5
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config;
6
+ @end