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,35 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ @class RenderContext;
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ /**
8
+ * Container for accessibility-related data extracted from RenderContext.
9
+ * Used by MarkdownAccessibilityElementBuilder to build VoiceOver elements.
10
+ */
11
+ @interface AccessibilityInfo : NSObject
12
+
13
+ // Headings
14
+ @property (nonatomic, copy, readonly) NSArray<NSValue *> *headingRanges;
15
+ @property (nonatomic, copy, readonly) NSArray<NSNumber *> *headingLevels;
16
+
17
+ // Links
18
+ @property (nonatomic, copy, readonly) NSArray<NSValue *> *linkRanges;
19
+ @property (nonatomic, copy, readonly) NSArray<NSString *> *linkURLs;
20
+
21
+ // Images
22
+ @property (nonatomic, copy, readonly) NSArray<NSValue *> *imageRanges;
23
+ @property (nonatomic, copy, readonly) NSArray<NSString *> *imageAltTexts;
24
+
25
+ // List items
26
+ @property (nonatomic, copy, readonly) NSArray<NSValue *> *listItemRanges;
27
+ @property (nonatomic, copy, readonly) NSArray<NSNumber *> *listItemPositions;
28
+ @property (nonatomic, copy, readonly) NSArray<NSNumber *> *listItemDepths;
29
+ @property (nonatomic, copy, readonly) NSArray<NSNumber *> *listItemOrdered; // YES = ordered, NO = bullet
30
+
31
+ + (instancetype)infoFromContext:(RenderContext *)context;
32
+
33
+ @end
34
+
35
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,24 @@
1
+ #import "AccessibilityInfo.h"
2
+ #import "RenderContext.h"
3
+
4
+ @implementation AccessibilityInfo
5
+
6
+ + (instancetype)infoFromContext:(RenderContext *)context
7
+ {
8
+ AccessibilityInfo *info = [[AccessibilityInfo alloc] init];
9
+ if (info) {
10
+ info->_headingRanges = [context.headingRanges copy];
11
+ info->_headingLevels = [context.headingLevels copy];
12
+ info->_linkRanges = [context.linkRanges copy];
13
+ info->_linkURLs = [context.linkURLs copy];
14
+ info->_imageRanges = [context.imageRanges copy];
15
+ info->_imageAltTexts = [context.imageAltTexts copy];
16
+ info->_listItemRanges = [context.listItemRanges copy];
17
+ info->_listItemPositions = [context.listItemPositions copy];
18
+ info->_listItemDepths = [context.listItemDepths copy];
19
+ info->_listItemOrdered = [context.listItemOrdered copy];
20
+ }
21
+ return info;
22
+ }
23
+
24
+ @end
@@ -0,0 +1,20 @@
1
+ #pragma once
2
+ #import "StyleConfig.h"
3
+ #import <UIKit/UIKit.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ extern NSString *const BlockquoteDepthAttributeName;
8
+ extern NSString *const BlockquoteBackgroundColorAttributeName;
9
+
10
+ @interface BlockquoteBorder : NSObject
11
+
12
+ - (instancetype)initWithConfig:(StyleConfig *)config;
13
+ - (void)drawBordersForGlyphRange:(NSRange)glyphsToShow
14
+ layoutManager:(NSLayoutManager *)layoutManager
15
+ textContainer:(NSTextContainer *)textContainer
16
+ atPoint:(CGPoint)origin;
17
+
18
+ @end
19
+
20
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,92 @@
1
+ #import "BlockquoteBorder.h"
2
+ #import "StyleConfig.h"
3
+
4
+ // Attribute constants for identifying blockquote segments in text storage
5
+ NSString *const BlockquoteDepthAttributeName = @"BlockquoteDepth";
6
+ NSString *const BlockquoteBackgroundColorAttributeName = @"BlockquoteBackgroundColor";
7
+
8
+ @implementation BlockquoteBorder {
9
+ StyleConfig *_config;
10
+ }
11
+
12
+ - (instancetype)initWithConfig:(StyleConfig *)config
13
+ {
14
+ if (self = [super init]) {
15
+ _config = config;
16
+ }
17
+ return self;
18
+ }
19
+
20
+ /**
21
+ * Main drawing entry point called by the LayoutManager.
22
+ * Iterates through line fragments to draw backgrounds and borders for nested blockquotes.
23
+ */
24
+ - (void)drawBordersForGlyphRange:(NSRange)glyphsToShow
25
+ layoutManager:(NSLayoutManager *)layoutManager
26
+ textContainer:(NSTextContainer *)textContainer
27
+ atPoint:(CGPoint)origin
28
+ {
29
+ NSTextStorage *textStorage = layoutManager.textStorage;
30
+ if (!textStorage || textStorage.length == 0) {
31
+ return;
32
+ }
33
+
34
+ // Cache configuration values to minimize pointer chasing and method lookups in the loop
35
+ StyleConfig *c = _config;
36
+ CGFloat borderWidth = c.blockquoteBorderWidth;
37
+ CGFloat gapWidth = c.blockquoteGapWidth;
38
+ CGFloat levelSpacing = borderWidth + gapWidth;
39
+ CGFloat containerWidth = textContainer.size.width;
40
+ UIColor *defaultBgColor = c.blockquoteBackgroundColor;
41
+ UIColor *borderColor = c.blockquoteBorderColor;
42
+
43
+ // Use a Bezier path to batch all vertical border rectangles into a single GPU draw call
44
+ UIBezierPath *borderPath = [UIBezierPath bezierPath];
45
+
46
+ [layoutManager
47
+ enumerateLineFragmentsForGlyphRange:glyphsToShow
48
+ usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *container,
49
+ NSRange glyphRange, BOOL *stop) {
50
+ // Map the glyph range back to character indices to retrieve attributes
51
+ NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphRange
52
+ actualGlyphRange:NULL];
53
+ if (charRange.location == NSNotFound || charRange.length == 0) {
54
+ return;
55
+ }
56
+
57
+ // Perform a single attribute lookup for the current line fragment
58
+ NSDictionary *attrs = [textStorage attributesAtIndex:charRange.location
59
+ effectiveRange:NULL];
60
+ NSNumber *depthNum = attrs[BlockquoteDepthAttributeName];
61
+
62
+ // If no depth is found, this fragment is not part of a blockquote
63
+ if (!depthNum) {
64
+ return;
65
+ }
66
+
67
+ NSInteger depth = [depthNum integerValue];
68
+ CGFloat baseY = origin.y + rect.origin.y;
69
+
70
+ // 1. Draw Background (Painter's algorithm: draw backgrounds before borders)
71
+ UIColor *bgColor = attrs[BlockquoteBackgroundColorAttributeName] ?: defaultBgColor;
72
+ if (bgColor && bgColor != [UIColor clearColor]) {
73
+ [bgColor setFill];
74
+ UIRectFill(CGRectMake(origin.x, baseY, containerWidth, rect.size.height));
75
+ }
76
+
77
+ // 2. Aggregate vertical borders into the batch path
78
+ for (NSInteger level = 0; level <= depth; level++) {
79
+ CGFloat borderX = origin.x + (levelSpacing * level);
80
+ CGRect borderRect = CGRectMake(borderX, baseY, borderWidth, rect.size.height);
81
+ [borderPath appendPath:[UIBezierPath bezierPathWithRect:borderRect]];
82
+ }
83
+ }];
84
+
85
+ // 3. Perform a single batch fill for all borders
86
+ if (!borderPath.isEmpty) {
87
+ [borderColor setFill];
88
+ [borderPath fill];
89
+ }
90
+ }
91
+
92
+ @end
@@ -0,0 +1,19 @@
1
+ #pragma once
2
+ #import "StyleConfig.h"
3
+ #import <UIKit/UIKit.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ extern NSString *const CodeAttributeName;
8
+
9
+ @interface CodeBackground : NSObject
10
+
11
+ - (instancetype)initWithConfig:(StyleConfig *)config;
12
+ - (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow
13
+ layoutManager:(NSLayoutManager *)layoutManager
14
+ textContainer:(NSTextContainer *)textContainer
15
+ atPoint:(CGPoint)origin;
16
+
17
+ @end
18
+
19
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,191 @@
1
+ #import "CodeBackground.h"
2
+ #import "RenderContext.h"
3
+
4
+ NSString *const CodeAttributeName = @"Code";
5
+
6
+ static const CGFloat kCodeBackgroundCornerRadius = 2.0;
7
+ static const CGFloat kCodeBackgroundBorderWidth = 0.5;
8
+
9
+ @implementation CodeBackground {
10
+ StyleConfig *_config;
11
+ }
12
+
13
+ - (instancetype)initWithConfig:(StyleConfig *)config
14
+ {
15
+ self = [super init];
16
+ if (self) {
17
+ _config = config;
18
+ }
19
+ return self;
20
+ }
21
+
22
+ - (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow
23
+ layoutManager:(NSLayoutManager *)layoutManager
24
+ textContainer:(NSTextContainer *)textContainer
25
+ atPoint:(CGPoint)origin
26
+ {
27
+ UIColor *backgroundColor = _config.codeBackgroundColor;
28
+ if (!backgroundColor)
29
+ return;
30
+
31
+ NSTextStorage *textStorage = layoutManager.textStorage;
32
+ NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphsToShow actualGlyphRange:NULL];
33
+ if (charRange.location == NSNotFound || charRange.length == 0)
34
+ return;
35
+
36
+ [textStorage enumerateAttribute:CodeAttributeName
37
+ inRange:NSMakeRange(0, textStorage.length)
38
+ options:0
39
+ usingBlock:^(id value, NSRange range, BOOL *stop) {
40
+ if (!value || range.length == 0)
41
+ return;
42
+ if (NSIntersectionRange(range, charRange).length == 0)
43
+ return;
44
+
45
+ [self drawCodeBackgroundForRange:range
46
+ layoutManager:layoutManager
47
+ textContainer:textContainer
48
+ atPoint:origin
49
+ backgroundColor:backgroundColor
50
+ borderColor:self->_config.codeBorderColor];
51
+ }];
52
+ }
53
+
54
+ - (void)drawCodeBackgroundForRange:(NSRange)range
55
+ layoutManager:(NSLayoutManager *)layoutManager
56
+ textContainer:(NSTextContainer *)textContainer
57
+ atPoint:(CGPoint)origin
58
+ backgroundColor:(UIColor *)backgroundColor
59
+ borderColor:(UIColor *)borderColor
60
+ {
61
+ NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:range actualCharacterRange:NULL];
62
+ if (glyphRange.location == NSNotFound || glyphRange.length == 0)
63
+ return;
64
+
65
+ CGFloat referenceHeight = [self findReferenceHeightForRange:range textStorage:layoutManager.textStorage];
66
+
67
+ [layoutManager
68
+ enumerateLineFragmentsForGlyphRange:glyphRange
69
+ usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *tc, NSRange lineRange,
70
+ BOOL *stop) {
71
+ NSRange intersect = NSIntersectionRange(lineRange, glyphRange);
72
+ if (intersect.length == 0)
73
+ return;
74
+
75
+ BOOL isFirst = (intersect.location == glyphRange.location);
76
+ BOOL isLast = (NSMaxRange(intersect) == NSMaxRange(glyphRange));
77
+
78
+ CGRect finalRect;
79
+ if (isFirst || isLast) {
80
+ // Precise bounds are only required for the start and end of the span
81
+ CGRect textRect = [layoutManager boundingRectForGlyphRange:intersect
82
+ inTextContainer:textContainer];
83
+ finalRect = CGRectMake(textRect.origin.x + origin.x, textRect.origin.y + origin.y,
84
+ textRect.size.width, textRect.size.height);
85
+
86
+ // For multi-line, extend the first line to the right edge of the fragment
87
+ if (isFirst && !isLast) {
88
+ finalRect.size.width =
89
+ (usedRect.origin.x + usedRect.size.width + origin.x) - finalRect.origin.x;
90
+ }
91
+ } else {
92
+ // OPTIMIZATION: Middle lines use the usedRect of the fragment directly
93
+ finalRect = CGRectMake(usedRect.origin.x + origin.x, usedRect.origin.y + origin.y,
94
+ usedRect.size.width, usedRect.size.height);
95
+ }
96
+
97
+ // Ensure consistent height and no gaps
98
+ if (finalRect.size.height < referenceHeight) {
99
+ finalRect.size.height = referenceHeight;
100
+ }
101
+
102
+ [self drawBackgroundAndBorders:finalRect
103
+ backgroundColor:backgroundColor
104
+ borderColor:borderColor
105
+ isFirst:isFirst
106
+ isLast:isLast];
107
+ }];
108
+ }
109
+
110
+ #pragma mark - Drawing Logic
111
+
112
+ - (void)drawBackgroundAndBorders:(CGRect)rect
113
+ backgroundColor:(UIColor *)backgroundColor
114
+ borderColor:(UIColor *)borderColor
115
+ isFirst:(BOOL)isFirst
116
+ isLast:(BOOL)isLast
117
+ {
118
+ UIBezierPath *path = [self pathForRect:rect isFirst:isFirst isLast:isLast];
119
+ [backgroundColor setFill];
120
+ [path fill];
121
+
122
+ if (borderColor) {
123
+ [borderColor setStroke];
124
+ UIBezierPath *strokePath =
125
+ (isFirst && isLast) ? path : [self openBorderPathForRect:rect isFirst:isFirst isLast:isLast];
126
+ strokePath.lineWidth = kCodeBackgroundBorderWidth;
127
+ strokePath.lineCapStyle = kCGLineCapRound;
128
+ strokePath.lineJoinStyle = kCGLineJoinRound;
129
+ [strokePath stroke];
130
+ }
131
+ }
132
+
133
+ - (UIBezierPath *)pathForRect:(CGRect)rect isFirst:(BOOL)isFirst isLast:(BOOL)isLast
134
+ {
135
+ UIRectCorner corners = 0;
136
+ if (isFirst)
137
+ corners |= (UIRectCornerTopLeft | UIRectCornerBottomLeft);
138
+ if (isLast)
139
+ corners |= (UIRectCornerTopRight | UIRectCornerBottomRight);
140
+
141
+ return [UIBezierPath bezierPathWithRoundedRect:rect
142
+ byRoundingCorners:corners
143
+ cornerRadii:CGSizeMake(kCodeBackgroundCornerRadius, kCodeBackgroundCornerRadius)];
144
+ }
145
+
146
+ - (UIBezierPath *)openBorderPathForRect:(CGRect)rect isFirst:(BOOL)isFirst isLast:(BOOL)isLast
147
+ {
148
+ UIBezierPath *path = [UIBezierPath bezierPath];
149
+ CGFloat r = kCodeBackgroundCornerRadius;
150
+ CGFloat inset = kCodeBackgroundBorderWidth / 2.0;
151
+ CGRect insetRect = CGRectInset(rect, inset, inset);
152
+
153
+ if (isFirst) {
154
+ [path moveToPoint:CGPointMake(CGRectGetMaxX(rect), insetRect.origin.y)];
155
+ [path addLineToPoint:CGPointMake(insetRect.origin.x + r, insetRect.origin.y)];
156
+ [path addQuadCurveToPoint:CGPointMake(insetRect.origin.x, insetRect.origin.y + r)
157
+ controlPoint:CGPointMake(insetRect.origin.x, insetRect.origin.y)];
158
+ [path addLineToPoint:CGPointMake(insetRect.origin.x, CGRectGetMaxY(insetRect) - r)];
159
+ [path addQuadCurveToPoint:CGPointMake(insetRect.origin.x + r, CGRectGetMaxY(insetRect))
160
+ controlPoint:CGPointMake(insetRect.origin.x, CGRectGetMaxY(insetRect))];
161
+ [path addLineToPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(insetRect))];
162
+ } else if (isLast) {
163
+ [path moveToPoint:CGPointMake(rect.origin.x, insetRect.origin.y)];
164
+ [path addLineToPoint:CGPointMake(CGRectGetMaxX(insetRect) - r, insetRect.origin.y)];
165
+ [path addQuadCurveToPoint:CGPointMake(CGRectGetMaxX(insetRect), insetRect.origin.y + r)
166
+ controlPoint:CGPointMake(CGRectGetMaxX(insetRect), insetRect.origin.y)];
167
+ [path addLineToPoint:CGPointMake(CGRectGetMaxX(insetRect), CGRectGetMaxY(insetRect) - r)];
168
+ [path addQuadCurveToPoint:CGPointMake(CGRectGetMaxX(insetRect) - r, CGRectGetMaxY(insetRect))
169
+ controlPoint:CGPointMake(CGRectGetMaxX(insetRect), CGRectGetMaxY(insetRect))];
170
+ [path addLineToPoint:CGPointMake(rect.origin.x, CGRectGetMaxY(insetRect))];
171
+ } else {
172
+ [path moveToPoint:CGPointMake(rect.origin.x, insetRect.origin.y)];
173
+ [path addLineToPoint:CGPointMake(CGRectGetMaxX(rect), insetRect.origin.y)];
174
+ [path moveToPoint:CGPointMake(rect.origin.x, CGRectGetMaxY(insetRect))];
175
+ [path addLineToPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(insetRect))];
176
+ }
177
+ return path;
178
+ }
179
+
180
+ #pragma mark - Helpers
181
+
182
+ - (CGFloat)findReferenceHeightForRange:(NSRange)range textStorage:(NSTextStorage *)textStorage
183
+ {
184
+ if (range.location == NSNotFound || range.length == 0 || !textStorage) {
185
+ return [_config paragraphFontSize] * 1.2;
186
+ }
187
+ NSNumber *lineHeightValue = [textStorage attribute:@"BlockLineHeight" atIndex:range.location effectiveRange:NULL];
188
+ return lineHeightValue ? [lineHeightValue doubleValue] : ([_config paragraphFontSize] * 1.2);
189
+ }
190
+
191
+ @end
@@ -0,0 +1,17 @@
1
+ #pragma once
2
+ #import "StyleConfig.h"
3
+ #import <UIKit/UIKit.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ @interface CodeBlockBackground : NSObject
8
+
9
+ - (instancetype)initWithConfig:(StyleConfig *)config;
10
+ - (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow
11
+ layoutManager:(NSLayoutManager *)layoutManager
12
+ textContainer:(NSTextContainer *)textContainer
13
+ atPoint:(CGPoint)origin;
14
+
15
+ @end
16
+
17
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,82 @@
1
+ #import "CodeBlockBackground.h"
2
+ #import "LastElementUtils.h"
3
+ #import "StyleConfig.h"
4
+
5
+ @implementation CodeBlockBackground {
6
+ StyleConfig *_config;
7
+ }
8
+
9
+ - (instancetype)initWithConfig:(StyleConfig *)config
10
+ {
11
+ if (self = [super init]) {
12
+ _config = config;
13
+ }
14
+ return self;
15
+ }
16
+
17
+ - (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow
18
+ layoutManager:(NSLayoutManager *)layoutManager
19
+ textContainer:(NSTextContainer *)textContainer
20
+ atPoint:(CGPoint)origin
21
+ {
22
+ NSTextStorage *textStorage = layoutManager.textStorage;
23
+ NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphsToShow actualGlyphRange:NULL];
24
+
25
+ [textStorage enumerateAttribute:CodeBlockAttributeName
26
+ inRange:charRange
27
+ options:0
28
+ usingBlock:^(id value, NSRange range, BOOL *stop) {
29
+ if (!value)
30
+ return;
31
+ [self drawCodeBlockBackgroundForRange:range
32
+ layoutManager:layoutManager
33
+ textContainer:textContainer
34
+ atPoint:origin];
35
+ }];
36
+ }
37
+
38
+ - (void)drawCodeBlockBackgroundForRange:(NSRange)range
39
+ layoutManager:(NSLayoutManager *)layoutManager
40
+ textContainer:(NSTextContainer *)textContainer
41
+ atPoint:(CGPoint)origin
42
+ {
43
+ NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:range actualCharacterRange:NULL];
44
+ CGRect blockRect = [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
45
+
46
+ if (CGRectIsEmpty(blockRect))
47
+ return;
48
+
49
+ blockRect.origin.x = origin.x;
50
+ blockRect.origin.y += origin.y;
51
+ blockRect.size.width = textContainer.size.width;
52
+
53
+ // We extend the background specifically to cover the bottom padding spacer,
54
+ // excluding any additional marginBottom applied via measurement.
55
+ BOOL isLastCodeBlock = (NSMaxRange(range) == layoutManager.textStorage.length);
56
+ if (isLastCodeBlock) {
57
+ blockRect.size.height += [_config codeBlockPadding];
58
+ }
59
+
60
+ CGFloat borderWidth = [_config codeBlockBorderWidth];
61
+ CGFloat borderRadius = [_config codeBlockBorderRadius];
62
+ CGFloat inset = borderWidth / 2.0;
63
+
64
+ CGRect insetRect = CGRectInset(blockRect, inset, inset);
65
+ UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:insetRect cornerRadius:MAX(0, borderRadius - inset)];
66
+
67
+ CGContextRef ctx = UIGraphicsGetCurrentContext();
68
+ CGContextSaveGState(ctx);
69
+ {
70
+ [[_config codeBlockBackgroundColor] setFill];
71
+ [path fill];
72
+
73
+ if (borderWidth > 0) {
74
+ [[_config codeBlockBorderColor] setStroke];
75
+ path.lineWidth = borderWidth;
76
+ path.lineJoinStyle = kCGLineJoinRound;
77
+ [path stroke];
78
+ }
79
+ }
80
+ CGContextRestoreGState(ctx);
81
+ }
82
+ @end
@@ -0,0 +1,22 @@
1
+ #pragma once
2
+ #import <Foundation/Foundation.h>
3
+ #import <UIKit/UIKit.h>
4
+
5
+ @class StyleConfig;
6
+
7
+ NS_ASSUME_NONNULL_BEGIN
8
+
9
+ #ifdef __cplusplus
10
+ extern "C" {
11
+ #endif
12
+
13
+ /// Builds edit menu with enhanced Copy (RTF/HTML/Markdown) and optional "Copy as Markdown"/"Copy Image URL".
14
+ UIMenu *buildEditMenuForSelection(NSAttributedString *attributedText, NSRange range, NSString *_Nullable cachedMarkdown,
15
+ StyleConfig *styleConfig, NSArray<UIMenuElement *> *suggestedActions)
16
+ API_AVAILABLE(ios(16.0));
17
+
18
+ #ifdef __cplusplus
19
+ }
20
+ #endif
21
+
22
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,118 @@
1
+ #import "EditMenuUtils.h"
2
+ #import "PasteboardUtils.h"
3
+ #import "StyleConfig.h"
4
+ #import <UIKit/UIPasteboard.h>
5
+
6
+ static NSString *const kMenuIdentifierStandardEdit = @"com.apple.menu.standard-edit";
7
+ static NSString *const kActionIdentifierCopy = @"com.swmansion.enriched.markdown.copy";
8
+ static NSString *const kActionIdentifierCopyMarkdown = @"com.swmansion.enriched.markdown.copyMarkdown";
9
+ static NSString *const kActionIdentifierCopyImageURL = @"com.swmansion.enriched.markdown.copyImageURL";
10
+
11
+ #pragma mark - Action Creators
12
+
13
+ static UIAction *createCopyAction(NSAttributedString *selectedText, NSString *markdown, StyleConfig *styleConfig)
14
+ {
15
+ return [UIAction actionWithTitle:@"Copy"
16
+ image:[UIImage systemImageNamed:@"doc.on.doc"]
17
+ identifier:kActionIdentifierCopy
18
+ handler:^(__kindof UIAction *action) {
19
+ copyAttributedStringToPasteboard(selectedText, markdown, styleConfig);
20
+ }];
21
+ }
22
+
23
+ static UIAction *_Nullable createCopyMarkdownAction(NSString *markdown)
24
+ {
25
+ if (markdown.length == 0)
26
+ return nil;
27
+
28
+ return [UIAction
29
+ actionWithTitle:@"Copy as Markdown"
30
+ image:[UIImage systemImageNamed:@"doc.text"]
31
+ identifier:kActionIdentifierCopyMarkdown
32
+ handler:^(__kindof UIAction *action) { [[UIPasteboard generalPasteboard] setString:markdown]; }];
33
+ }
34
+
35
+ static UIAction *_Nullable createCopyImageURLAction(NSArray<NSString *> *imageURLs)
36
+ {
37
+ if (imageURLs.count == 0)
38
+ return nil;
39
+
40
+ NSString *urlsToCopy = [imageURLs componentsJoinedByString:@"\n"];
41
+ NSString *title = (imageURLs.count == 1)
42
+ ? @"Copy Image URL"
43
+ : [NSString stringWithFormat:@"Copy %lu Image URLs", (unsigned long)imageURLs.count];
44
+
45
+ return [UIAction
46
+ actionWithTitle:title
47
+ image:[UIImage systemImageNamed:@"link"]
48
+ identifier:kActionIdentifierCopyImageURL
49
+ handler:^(__kindof UIAction *action) { [[UIPasteboard generalPasteboard] setString:urlsToCopy]; }];
50
+ }
51
+
52
+ #pragma mark - Menu Building Helpers
53
+
54
+ static UIMenu *createEnhancedStandardEditMenu(UIMenu *originalMenu, UIAction *copyAction)
55
+ {
56
+ return [UIMenu menuWithTitle:originalMenu.title
57
+ image:originalMenu.image
58
+ identifier:originalMenu.identifier
59
+ options:originalMenu.options
60
+ children:@[ copyAction ]];
61
+ }
62
+
63
+ static void addOptionalAction(NSMutableArray<UIMenuElement *> *array, UIAction *_Nullable action)
64
+ {
65
+ if (action) {
66
+ [array addObject:action];
67
+ }
68
+ }
69
+
70
+ static void insertOptionalAction(NSMutableArray<UIMenuElement *> *array, UIAction *_Nullable action, NSUInteger index)
71
+ {
72
+ if (action) {
73
+ [array insertObject:action atIndex:index];
74
+ }
75
+ }
76
+
77
+ #pragma mark - Public API
78
+
79
+ UIMenu *buildEditMenuForSelection(NSAttributedString *attributedText, NSRange range, NSString *_Nullable cachedMarkdown,
80
+ StyleConfig *styleConfig, NSArray<UIMenuElement *> *suggestedActions)
81
+ API_AVAILABLE(ios(16.0))
82
+ {
83
+ NSAttributedString *selectedText = [attributedText attributedSubstringFromRange:range];
84
+ NSString *markdown = markdownForRange(attributedText, range, cachedMarkdown);
85
+ NSArray<NSString *> *imageURLs = imageURLsInRange(attributedText, range);
86
+
87
+ UIAction *copyAction = createCopyAction(selectedText, markdown, styleConfig);
88
+ UIAction *copyMarkdownAction = createCopyMarkdownAction(markdown);
89
+ UIAction *copyImageURLAction = createCopyImageURLAction(imageURLs);
90
+
91
+ NSMutableArray<UIMenuElement *> *result = [NSMutableArray array];
92
+ BOOL foundStandardEditMenu = NO;
93
+
94
+ for (UIMenuElement *element in suggestedActions) {
95
+ if ([element isKindOfClass:[UIMenu class]]) {
96
+ UIMenu *menu = (UIMenu *)element;
97
+
98
+ if ([menu.identifier isEqualToString:kMenuIdentifierStandardEdit]) {
99
+ // Replace standard Copy with our enhanced version
100
+ [result addObject:createEnhancedStandardEditMenu(menu, copyAction)];
101
+ addOptionalAction(result, copyMarkdownAction);
102
+ addOptionalAction(result, copyImageURLAction);
103
+ foundStandardEditMenu = YES;
104
+ continue;
105
+ }
106
+ }
107
+ [result addObject:element];
108
+ }
109
+
110
+ // Fallback if standard-edit menu wasn't found
111
+ if (!foundStandardEditMenu) {
112
+ [result insertObject:copyAction atIndex:0];
113
+ insertOptionalAction(result, copyMarkdownAction, 1);
114
+ addOptionalAction(result, copyImageURLAction);
115
+ }
116
+
117
+ return [UIMenu menuWithChildren:result];
118
+ }
@@ -0,0 +1,25 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <UIKit/UIKit.h>
3
+
4
+ @class BlockStyle;
5
+ @class RenderContext;
6
+
7
+ NS_ASSUME_NONNULL_BEGIN
8
+
9
+ #ifdef __cplusplus
10
+ extern "C" {
11
+ #endif
12
+
13
+ /** Returns a cached UIFont from BlockStyle properties via RenderContext. */
14
+ extern UIFont *cachedFontFromBlockStyle(BlockStyle *blockStyle, RenderContext *context);
15
+
16
+ /** Returns the font scale multiplier, capped by maxFontSizeMultiplier.
17
+ * Uses React Native's RCTFontSizeMultiplier() internally.
18
+ * @param maxFontSizeMultiplier Values >= 1.0 cap the result, < 1.0 means no cap. */
19
+ extern CGFloat RCTFontSizeMultiplierWithMax(CGFloat maxFontSizeMultiplier);
20
+
21
+ #ifdef __cplusplus
22
+ }
23
+ #endif
24
+
25
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,27 @@
1
+ #import "FontUtils.h"
2
+ #import "RenderContext.h"
3
+ #import <React/RCTUtils.h>
4
+
5
+ UIFont *cachedFontFromBlockStyle(BlockStyle *blockStyle, RenderContext *context)
6
+ {
7
+ if (!blockStyle) {
8
+ return nil;
9
+ }
10
+ if (blockStyle.cachedFont) {
11
+ return blockStyle.cachedFont;
12
+ }
13
+ return [context cachedFontForSize:blockStyle.fontSize family:blockStyle.fontFamily weight:blockStyle.fontWeight];
14
+ }
15
+
16
+ CGFloat RCTFontSizeMultiplierWithMax(CGFloat maxFontSizeMultiplier)
17
+ {
18
+ CGFloat multiplier = RCTFontSizeMultiplier();
19
+
20
+ // Apply maxFontSizeMultiplier cap if >= 1.0
21
+ // Values < 1.0 (including 0 and NaN) mean no cap is applied
22
+ if (!isnan(maxFontSizeMultiplier) && maxFontSizeMultiplier >= 1.0) {
23
+ return fmin(maxFontSizeMultiplier, multiplier);
24
+ }
25
+
26
+ return multiplier;
27
+ }