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,105 @@
1
+ #import "HeadingRenderer.h"
2
+ #import "FontUtils.h"
3
+ #import "ParagraphStyleUtils.h"
4
+ #import "RenderContext.h"
5
+ #import "RendererFactory.h"
6
+ #import "RuntimeKeys.h"
7
+ #import "StyleConfig.h"
8
+
9
+ // Lightweight struct to hold style data without object overhead
10
+ typedef struct {
11
+ __unsafe_unretained UIFont *font;
12
+ __unsafe_unretained UIColor *color;
13
+ CGFloat marginTop;
14
+ CGFloat marginBottom;
15
+ CGFloat lineHeight;
16
+ NSTextAlignment textAlign;
17
+ } HeadingStyle;
18
+
19
+ static NSString *const kHeadingTypes[] = {nil, @"heading-1", @"heading-2", @"heading-3",
20
+ @"heading-4", @"heading-5", @"heading-6"};
21
+
22
+ @implementation HeadingRenderer {
23
+ RendererFactory *_rendererFactory;
24
+ StyleConfig *_config;
25
+ }
26
+
27
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
28
+ {
29
+ if (self = [super init]) {
30
+ _rendererFactory = rendererFactory;
31
+ _config = (StyleConfig *)config;
32
+ }
33
+ return self;
34
+ }
35
+
36
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
37
+ {
38
+ NSInteger level = [node.attributes[@"level"] integerValue];
39
+ if (level < 1 || level > 6)
40
+ level = 1;
41
+
42
+ HeadingStyle style = [self styleForLevel:level];
43
+ [context setBlockStyle:BlockTypeHeading font:style.font color:style.color headingLevel:level];
44
+
45
+ NSUInteger start = output.length;
46
+ NSUInteger contentStart = start;
47
+
48
+ // Spacing at the very start of the document requires a spacer character (index 0 check)
49
+ if (start == 0) {
50
+ NSUInteger offset = applyBlockSpacingBefore(output, 0, style.marginTop);
51
+ contentStart += offset;
52
+ start += offset;
53
+ }
54
+
55
+ @try {
56
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
57
+ } @finally {
58
+ [context clearBlockStyle];
59
+ }
60
+
61
+ NSRange range = NSMakeRange(start, output.length - start);
62
+ if (range.length == 0)
63
+ return;
64
+
65
+ // Register heading for accessibility
66
+ NSString *headingText = [[output attributedSubstringFromRange:range] string];
67
+ [context registerHeadingRange:range level:level text:headingText];
68
+
69
+ // Metadata attribute used for post-processing (e.g., Export to Markdown/HTML)
70
+ [output addAttribute:MarkdownTypeAttributeName value:kHeadingTypes[level] range:range];
71
+
72
+ applyLineHeight(output, range, style.lineHeight);
73
+ applyTextAlignment(output, range, style.textAlign);
74
+
75
+ // Use paragraphSpacingBefore for internal elements; applyBlockSpacingBefore handles index 0
76
+ if (contentStart != 1) {
77
+ applyParagraphSpacingBefore(output, range, style.marginTop);
78
+ }
79
+ applyParagraphSpacingAfter(output, start, style.marginBottom);
80
+ }
81
+
82
+ #pragma mark - Style Mapping
83
+
84
+ - (HeadingStyle)styleForLevel:(NSInteger)level
85
+ {
86
+ StyleConfig *c = _config;
87
+ switch (level) {
88
+ case 1:
89
+ return (HeadingStyle){c.h1Font, c.h1Color, c.h1MarginTop, c.h1MarginBottom, c.h1LineHeight, c.h1TextAlign};
90
+ case 2:
91
+ return (HeadingStyle){c.h2Font, c.h2Color, c.h2MarginTop, c.h2MarginBottom, c.h2LineHeight, c.h2TextAlign};
92
+ case 3:
93
+ return (HeadingStyle){c.h3Font, c.h3Color, c.h3MarginTop, c.h3MarginBottom, c.h3LineHeight, c.h3TextAlign};
94
+ case 4:
95
+ return (HeadingStyle){c.h4Font, c.h4Color, c.h4MarginTop, c.h4MarginBottom, c.h4LineHeight, c.h4TextAlign};
96
+ case 5:
97
+ return (HeadingStyle){c.h5Font, c.h5Color, c.h5MarginTop, c.h5MarginBottom, c.h5LineHeight, c.h5TextAlign};
98
+ case 6:
99
+ return (HeadingStyle){c.h6Font, c.h6Color, c.h6MarginTop, c.h6MarginBottom, c.h6LineHeight, c.h6TextAlign};
100
+ default:
101
+ return [self styleForLevel:1];
102
+ }
103
+ }
104
+
105
+ @end
@@ -0,0 +1,12 @@
1
+ #pragma once
2
+ #import "NodeRenderer.h"
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ @interface ImageRenderer : NSObject <NodeRenderer>
7
+
8
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config;
9
+
10
+ @end
11
+
12
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,83 @@
1
+ #import "ImageRenderer.h"
2
+ #import "EnrichedMarkdownImageAttachment.h"
3
+ #import "MarkdownASTNode.h"
4
+ #import "RenderContext.h"
5
+ #import "RendererFactory.h"
6
+ #import "StyleConfig.h"
7
+
8
+ static const unichar kLineBreak = '\n';
9
+ static const unichar kZeroWidthSpace = 0x200B;
10
+
11
+ @implementation ImageRenderer {
12
+ RendererFactory *_rendererFactory;
13
+ StyleConfig *_config;
14
+ }
15
+
16
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
17
+ {
18
+ if (self = [super init]) {
19
+ _rendererFactory = rendererFactory;
20
+ _config = (StyleConfig *)config;
21
+ }
22
+ return self;
23
+ }
24
+
25
+ #pragma mark - Rendering
26
+
27
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
28
+ {
29
+ NSString *imageURL = node.attributes[@"url"];
30
+ if (!imageURL || imageURL.length == 0) {
31
+ return;
32
+ }
33
+
34
+ BOOL isInline = [self isInlineImageInOutput:output];
35
+ EnrichedMarkdownImageAttachment *attachment = [[EnrichedMarkdownImageAttachment alloc] initWithImageURL:imageURL
36
+ config:_config
37
+ isInline:isInline];
38
+
39
+ NSUInteger startIndex = output.length;
40
+
41
+ NSAttributedString *imageString = [NSAttributedString attributedStringWithAttachment:attachment];
42
+ [output appendAttributedString:imageString];
43
+
44
+ // Extract alt text from children (![alt text](url) - "alt text" is in children)
45
+ NSString *altText = [self extractTextFromNode:node];
46
+ NSRange imageRange = NSMakeRange(startIndex, output.length - startIndex);
47
+ [context registerImageRange:imageRange altText:altText url:imageURL];
48
+ }
49
+
50
+ #pragma mark - Private Helpers
51
+
52
+ - (NSString *)extractTextFromNode:(MarkdownASTNode *)node
53
+ {
54
+ if (!node)
55
+ return @"";
56
+
57
+ NSMutableString *buffer = [NSMutableString string];
58
+ [self _appendChildTextFromNode:node toBuffer:buffer];
59
+ return [buffer copy];
60
+ }
61
+
62
+ - (void)_appendChildTextFromNode:(MarkdownASTNode *)node toBuffer:(NSMutableString *)buffer
63
+ {
64
+ if (node.content.length > 0) {
65
+ [buffer appendString:node.content];
66
+ }
67
+
68
+ for (MarkdownASTNode *child in node.children) {
69
+ [self _appendChildTextFromNode:child toBuffer:buffer];
70
+ }
71
+ }
72
+
73
+ - (BOOL)isInlineImageInOutput:(NSAttributedString *)output
74
+ {
75
+ if (output.length == 0) {
76
+ return NO;
77
+ }
78
+
79
+ unichar lastChar = [output.string characterAtIndex:output.length - 1];
80
+ return (lastChar != kLineBreak && lastChar != kZeroWidthSpace);
81
+ }
82
+
83
+ @end
@@ -0,0 +1,7 @@
1
+ #import "MarkdownASTNode.h"
2
+ #import "NodeRenderer.h"
3
+ #import "RenderContext.h"
4
+
5
+ @interface LinkRenderer : NSObject <NodeRenderer>
6
+ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config;
7
+ @end
@@ -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,103 @@
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
+ context.listItemNumber++;
32
+ const NSInteger currentPosition = context.listItemNumber;
33
+ const NSInteger currentDepth = context.listDepth; // 1-based (1 = top level)
34
+
35
+ const NSUInteger startLocation = output.length;
36
+
37
+ // Render the actual content of the list item (text, bolding, etc.)
38
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
39
+
40
+ // Ensure every list item ends with a newline to prevent paragraph merging
41
+ if (output.length > startLocation && ![output.string hasSuffix:@"\n"]) {
42
+ [output appendAttributedString:kNewlineAttributedString];
43
+ }
44
+
45
+ const NSRange itemRange = NSMakeRange(startLocation, output.length - startLocation);
46
+ if (itemRange.length == 0)
47
+ return;
48
+
49
+ // Informs MarkdownAccessibilityElementBuilder about the specific boundaries of this list item
50
+ [context registerListItemRange:itemRange
51
+ position:currentPosition
52
+ depth:currentDepth
53
+ isOrdered:(context.listType == ListTypeOrdered)];
54
+
55
+ // currentDepth - 1 handles the horizontal offset for nested lists
56
+ const NSInteger nestingLevel = currentDepth - 1;
57
+ const CGFloat baseMarkerWidth = (context.listType == ListTypeOrdered) ? [_config effectiveListMarginLeftForNumber]
58
+ : [_config effectiveListMarginLeftForBullet];
59
+
60
+ const CGFloat totalIndent =
61
+ baseMarkerWidth + [_config effectiveListGapWidth] + (nestingLevel * [_config listStyleMarginLeft]);
62
+
63
+ const CGFloat lineHeightConfig = [_config listStyleLineHeight];
64
+
65
+ // Boxing metadata for attributed string storage
66
+ NSDictionary *metadata = @{
67
+ ListDepthAttribute : @(nestingLevel),
68
+ ListTypeAttribute : @(context.listType),
69
+ ListItemNumberAttribute : @(currentPosition)
70
+ };
71
+
72
+ // We enumerate to ensure we don't overwrite styles of nested sub-lists
73
+ // that may have already been rendered inside this item.
74
+ [output enumerateAttribute:ListDepthAttribute
75
+ inRange:itemRange
76
+ options:0
77
+ usingBlock:^(id depthAttr, NSRange range, BOOL *stop) {
78
+ // If a segment already has a Depth attribute higher than our current level,
79
+ // it belongs to a nested list and we should skip it to preserve its styling.
80
+ if (depthAttr && [depthAttr integerValue] > nestingLevel) {
81
+ return;
82
+ }
83
+
84
+ NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
85
+ style.firstLineHeadIndent = totalIndent;
86
+ style.headIndent = totalIndent;
87
+
88
+ // Apply line height if configured
89
+ UIFont *currentFont = [output attribute:NSFontAttributeName
90
+ atIndex:range.location
91
+ effectiveRange:NULL];
92
+ if (lineHeightConfig > 0 && currentFont) {
93
+ style.lineHeightMultiple = lineHeightConfig / currentFont.pointSize;
94
+ }
95
+
96
+ NSMutableDictionary *attributesToApply = [metadata mutableCopy];
97
+ attributesToApply[NSParagraphStyleAttributeName] = style;
98
+
99
+ [output addAttributes:attributesToApply range:range];
100
+ }];
101
+ }
102
+
103
+ @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,70 @@
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
+ const NSInteger prevDepth = context.listDepth;
31
+ const ListType prevType = context.listType;
32
+ const NSInteger prevNum = context.listItemNumber;
33
+
34
+ const NSUInteger startLocation = output.length;
35
+ NSUInteger contentStart = startLocation;
36
+
37
+ if (prevDepth == 0) {
38
+ // Apply top margin for root-level list
39
+ contentStart += applyBlockSpacingBefore(output, startLocation, _config.listStyleMarginTop);
40
+ } else if (output.length > 0 && ![output.string hasSuffix:@"\n"]) {
41
+ // Ensure nested lists start on a new line
42
+ [output appendAttributedString:kNewlineAttributedString];
43
+ }
44
+
45
+ context.listDepth = prevDepth + 1;
46
+ context.listType = _isOrdered ? ListTypeOrdered : ListTypeUnordered;
47
+ context.listItemNumber = 0; // Reset counter for this specific list level
48
+
49
+ [context setBlockStyle:_isOrdered ? BlockTypeOrderedList : BlockTypeUnorderedList
50
+ font:_config.listStyleFont
51
+ color:_config.listStyleColor
52
+ headingLevel:0];
53
+
54
+ @try {
55
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
56
+ } @finally {
57
+ context.listDepth = prevDepth;
58
+ context.listType = prevType;
59
+ context.listItemNumber = prevNum;
60
+
61
+ if (prevDepth == 0) {
62
+ [context clearBlockStyle];
63
+
64
+ // Apply bottom margin for root-level list
65
+ applyBlockSpacingAfter(output, _config.listStyleMarginBottom);
66
+ }
67
+ }
68
+ }
69
+
70
+ @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,80 @@
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
+ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
24
+ {
25
+ // Only set block style if a parent element (e.g. List, Blockquote) hasn't already established one
26
+ BOOL isTopLevel = (context.currentBlockType == BlockTypeNone);
27
+
28
+ if (isTopLevel) {
29
+ [context setBlockStyle:BlockTypeParagraph font:_config.paragraphFont color:_config.paragraphColor headingLevel:0];
30
+ }
31
+
32
+ NSUInteger start = output.length;
33
+ BOOL shouldApplyMargin =
34
+ (context.currentBlockType == BlockTypeNone || context.currentBlockType == BlockTypeParagraph);
35
+
36
+ // Detect if the paragraph is a wrapper for a standalone image to use image-specific spacing
37
+ BOOL isBlockImage = (node.children.count == 1 && ((MarkdownASTNode *)node.children[0]).type == MarkdownNodeTypeImage);
38
+ CGFloat marginTop = isBlockImage ? _config.imageMarginTop : _config.paragraphMarginTop;
39
+
40
+ NSUInteger contentStart = start;
41
+
42
+ // Handle leading margin for the first element in the document (Index 0 check)
43
+ if (shouldApplyMargin && start == 0) {
44
+ NSUInteger offset = applyBlockSpacingBefore(output, 0, marginTop);
45
+ contentStart += offset;
46
+ start += offset;
47
+ }
48
+
49
+ @try {
50
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
51
+ } @finally {
52
+ if (isTopLevel) {
53
+ [context clearBlockStyle];
54
+ }
55
+ }
56
+
57
+ if (output.length <= start)
58
+ return;
59
+ NSRange range = NSMakeRange(start, output.length - start);
60
+
61
+ // Avoid standard line height on block images to prevent vertical alignment issues
62
+ if (!isBlockImage) {
63
+ applyLineHeight(output, range, _config.paragraphLineHeight);
64
+ }
65
+
66
+ applyTextAlignment(output, range, _config.paragraphTextAlign);
67
+
68
+ // Apply marginTop for non-index-0 elements using paragraphSpacingBefore
69
+ if (shouldApplyMargin && contentStart != 1) {
70
+ applyParagraphSpacingBefore(output, range, marginTop);
71
+ }
72
+
73
+ CGFloat marginBottom = 0;
74
+ if (shouldApplyMargin) {
75
+ marginBottom = isBlockImage ? _config.imageMarginBottom : _config.paragraphMarginBottom;
76
+ }
77
+ applyParagraphSpacingAfter(output, start, marginBottom);
78
+ }
79
+
80
+ @end
@@ -0,0 +1,105 @@
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, strong) NSMutableArray<NSValue *> *headingRanges;
29
+ @property (nonatomic, strong) NSMutableArray<NSNumber *> *headingLevels;
30
+ @property (nonatomic, strong) NSMutableArray<NSValue *> *imageRanges;
31
+ @property (nonatomic, strong) NSMutableArray<NSString *> *imageAltTexts;
32
+ @property (nonatomic, strong) NSMutableArray<NSString *> *imageURLs;
33
+ @property (nonatomic, strong) NSMutableArray<NSValue *> *listItemRanges;
34
+ @property (nonatomic, strong) NSMutableArray<NSNumber *> *listItemPositions; // Position in parent list (1, 2, 3...)
35
+ @property (nonatomic, strong) NSMutableArray<NSNumber *> *listItemDepths; // Nesting depth (1 = top level, 2+ = nested)
36
+ @property (nonatomic, strong) NSMutableArray<NSNumber *> *listItemOrdered; // YES = ordered, NO = unordered (bullet)
37
+ @property (nonatomic, assign) BlockType currentBlockType;
38
+ @property (nonatomic, strong) BlockStyle *currentBlockStyle;
39
+ @property (nonatomic, assign) NSInteger currentHeadingLevel;
40
+ @property (nonatomic, assign) NSInteger blockquoteDepth;
41
+ @property (nonatomic, assign) NSInteger listDepth;
42
+ @property (nonatomic, assign) ListType listType;
43
+ @property (nonatomic, assign) NSInteger listItemNumber;
44
+ @property (nonatomic, assign) BOOL allowFontScaling;
45
+ @property (nonatomic, assign) CGFloat maxFontSizeMultiplier;
46
+
47
+ - (instancetype)init;
48
+ - (void)reset;
49
+
50
+ - (UIFont *)cachedFontForSize:(CGFloat)fontSize family:(NSString *)fontFamily weight:(NSString *)fontWeight;
51
+ - (NSMutableParagraphStyle *)spacerStyleWithHeight:(CGFloat)height spacing:(CGFloat)spacing;
52
+ - (NSMutableParagraphStyle *)blockSpacerStyleWithMargin:(CGFloat)margin;
53
+ - (void)registerLinkRange:(NSRange)range url:(NSString *)url;
54
+ - (void)registerHeadingRange:(NSRange)range level:(NSInteger)level text:(NSString *)text;
55
+ - (void)registerImageRange:(NSRange)range altText:(NSString *)altText url:(NSString *)url;
56
+ - (void)registerListItemRange:(NSRange)range
57
+ position:(NSInteger)position
58
+ depth:(NSInteger)depth
59
+ isOrdered:(BOOL)isOrdered;
60
+ - (void)setBlockStyle:(BlockType)type
61
+ fontSize:(CGFloat)fontSize
62
+ fontFamily:(NSString *)fontFamily
63
+ fontWeight:(NSString *)fontWeight
64
+ color:(UIColor *)color;
65
+ - (void)setBlockStyle:(BlockType)type
66
+ fontSize:(CGFloat)fontSize
67
+ fontFamily:(NSString *)fontFamily
68
+ fontWeight:(NSString *)fontWeight
69
+ color:(UIColor *)color
70
+ headingLevel:(NSInteger)headingLevel;
71
+ - (void)setBlockStyle:(BlockType)type font:(UIFont *)font color:(UIColor *)color headingLevel:(NSInteger)headingLevel;
72
+ - (BlockStyle *)getBlockStyle;
73
+ - (NSDictionary *)getTextAttributes;
74
+ - (void)clearBlockStyle;
75
+
76
+ /**
77
+ * Checks if colors should be preserved based on existing attributes.
78
+ * Returns YES if the text is inside a link or inline code, which means
79
+ * we should preserve their colors instead of applying new colors.
80
+ */
81
+ + (BOOL)shouldPreserveColors:(NSDictionary *)existingAttributes;
82
+
83
+ /**
84
+ * Calculates the color that strong would use based on the configured strong color and block style.
85
+ * Uses strongColor if explicitly set (different from block color), otherwise uses block color.
86
+ */
87
+ + (UIColor *)calculateStrongColor:(UIColor *)configStrongColor blockColor:(UIColor *)blockColor;
88
+
89
+ /**
90
+ * Calculates the range for content rendered between start and current output length.
91
+ * Returns a range with length 0 if no content was rendered.
92
+ */
93
+ + (NSRange)rangeForRenderedContent:(NSMutableAttributedString *)output start:(NSUInteger)start;
94
+
95
+ /**
96
+ * Applies font and color attributes conditionally, only updating if they've changed.
97
+ * Returns YES if any attributes were updated, NO otherwise.
98
+ */
99
+ + (BOOL)applyFontAndColorAttributes:(NSMutableAttributedString *)output
100
+ range:(NSRange)range
101
+ font:(UIFont *)font
102
+ color:(UIColor *)color
103
+ existingAttributes:(NSDictionary *)existingAttributes
104
+ shouldPreserveColors:(BOOL)shouldPreserveColors;
105
+ @end