@mathers101/react-native-render-rich-text 0.1.10 → 0.1.14

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 (327) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +140 -140
  3. package/ReactNativeRenderRichText.podspec +23 -23
  4. package/android/build.gradle +86 -86
  5. package/android/consumer-rules.pro +3 -3
  6. package/android/src/main/AndroidManifest.xml +2 -2
  7. package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextLayoutManager.kt +24 -24
  8. package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextPackage.kt +13 -13
  9. package/android/src/main/java/com/renderrichtext/MeasurementStore.kt +644 -644
  10. package/android/src/main/java/com/renderrichtext/accessibility/AccessibilityLabels.kt +17 -17
  11. package/android/src/main/java/com/renderrichtext/accessibility/AccessibleTiptapTextView.kt +112 -112
  12. package/android/src/main/java/com/renderrichtext/accessibility/TiptapAccessibilityHelper.kt +576 -576
  13. package/android/src/main/java/com/renderrichtext/events/CodeBlockPressEvent.kt +24 -24
  14. package/android/src/main/java/com/renderrichtext/events/ContextMenuItemPressEvent.kt +28 -28
  15. package/android/src/main/java/com/renderrichtext/events/CopyPressEvent.kt +24 -24
  16. package/android/src/main/java/com/renderrichtext/events/ImagePressEvent.kt +25 -25
  17. package/android/src/main/java/com/renderrichtext/events/LatexErrorEvent.kt +29 -29
  18. package/android/src/main/java/com/renderrichtext/events/LinkLongPressEvent.kt +23 -23
  19. package/android/src/main/java/com/renderrichtext/events/LinkPressEvent.kt +23 -23
  20. package/android/src/main/java/com/renderrichtext/events/MentionLongPressEvent.kt +25 -25
  21. package/android/src/main/java/com/renderrichtext/events/MentionPressEvent.kt +25 -25
  22. package/android/src/main/java/com/renderrichtext/events/TaskListItemPressEvent.kt +26 -26
  23. package/android/src/main/java/com/renderrichtext/math/LatexErrorReporter.kt +9 -9
  24. package/android/src/main/java/com/renderrichtext/parser/Parser.kt +59 -59
  25. package/android/src/main/java/com/renderrichtext/parser/TextLinkMatching.kt +35 -35
  26. package/android/src/main/java/com/renderrichtext/renderer/BaselineShiftRenderer.kt +45 -45
  27. package/android/src/main/java/com/renderrichtext/renderer/BlankLineRenderer.kt +39 -39
  28. package/android/src/main/java/com/renderrichtext/renderer/BlockquoteRenderer.kt +243 -243
  29. package/android/src/main/java/com/renderrichtext/renderer/BlockquoteTextRenderer.kt +56 -56
  30. package/android/src/main/java/com/renderrichtext/renderer/CodeBlockRenderer.kt +135 -135
  31. package/android/src/main/java/com/renderrichtext/renderer/CodeRenderer.kt +37 -37
  32. package/android/src/main/java/com/renderrichtext/renderer/EmphasisRenderer.kt +28 -28
  33. package/android/src/main/java/com/renderrichtext/renderer/HeadingRenderer.kt +81 -81
  34. package/android/src/main/java/com/renderrichtext/renderer/HighlightRenderer.kt +26 -26
  35. package/android/src/main/java/com/renderrichtext/renderer/LineBreakRenderer.kt +17 -17
  36. package/android/src/main/java/com/renderrichtext/renderer/LinkRenderer.kt +90 -90
  37. package/android/src/main/java/com/renderrichtext/renderer/ListContextManager.kt +109 -109
  38. package/android/src/main/java/com/renderrichtext/renderer/ListItemRenderer.kt +211 -211
  39. package/android/src/main/java/com/renderrichtext/renderer/ListRenderer.kt +133 -133
  40. package/android/src/main/java/com/renderrichtext/renderer/MentionRenderer.kt +85 -85
  41. package/android/src/main/java/com/renderrichtext/renderer/ParagraphRenderer.kt +91 -91
  42. package/android/src/main/java/com/renderrichtext/renderer/Renderer.kt +148 -148
  43. package/android/src/main/java/com/renderrichtext/renderer/SoftBreakRenderer.kt +17 -17
  44. package/android/src/main/java/com/renderrichtext/renderer/SpanStyleCache.kt +143 -143
  45. package/android/src/main/java/com/renderrichtext/renderer/StrikethroughRenderer.kt +28 -28
  46. package/android/src/main/java/com/renderrichtext/renderer/StrongRenderer.kt +28 -28
  47. package/android/src/main/java/com/renderrichtext/renderer/TextRenderer.kt +31 -31
  48. package/android/src/main/java/com/renderrichtext/renderer/ThematicBreakRenderer.kt +46 -46
  49. package/android/src/main/java/com/renderrichtext/renderer/UnderlineRenderer.kt +28 -28
  50. package/android/src/main/java/com/renderrichtext/spans/BaseListSpan.kt +164 -164
  51. package/android/src/main/java/com/renderrichtext/spans/BaselineShiftSpan.kt +27 -27
  52. package/android/src/main/java/com/renderrichtext/spans/BlockquoteSpan.kt +302 -302
  53. package/android/src/main/java/com/renderrichtext/spans/CodeBackgroundSpan.kt +205 -205
  54. package/android/src/main/java/com/renderrichtext/spans/CodeBlockSpan.kt +216 -216
  55. package/android/src/main/java/com/renderrichtext/spans/CodeSpan.kt +32 -32
  56. package/android/src/main/java/com/renderrichtext/spans/EmphasisSpan.kt +42 -42
  57. package/android/src/main/java/com/renderrichtext/spans/FadeInSpan.kt +29 -29
  58. package/android/src/main/java/com/renderrichtext/spans/HeadingSpan.kt +38 -38
  59. package/android/src/main/java/com/renderrichtext/spans/HighlightSpan.kt +80 -80
  60. package/android/src/main/java/com/renderrichtext/spans/ImageSpan.kt +481 -481
  61. package/android/src/main/java/com/renderrichtext/spans/InlineBackgroundGeometry.kt +42 -42
  62. package/android/src/main/java/com/renderrichtext/spans/LineHeightSpan.kt +39 -39
  63. package/android/src/main/java/com/renderrichtext/spans/LinkPillIconCache.kt +154 -154
  64. package/android/src/main/java/com/renderrichtext/spans/LinkPillSpan.kt +381 -381
  65. package/android/src/main/java/com/renderrichtext/spans/LinkSpan.kt +68 -68
  66. package/android/src/main/java/com/renderrichtext/spans/ListItemSpacingSpan.kt +33 -33
  67. package/android/src/main/java/com/renderrichtext/spans/ListMarkerAnchorSpan.kt +38 -38
  68. package/android/src/main/java/com/renderrichtext/spans/MarginBottomSpan.kt +76 -76
  69. package/android/src/main/java/com/renderrichtext/spans/MentionChipSpan.kt +132 -132
  70. package/android/src/main/java/com/renderrichtext/spans/MentionSpan.kt +46 -46
  71. package/android/src/main/java/com/renderrichtext/spans/OrderedListSpan.kt +127 -127
  72. package/android/src/main/java/com/renderrichtext/spans/SpoilerSpan.kt +33 -33
  73. package/android/src/main/java/com/renderrichtext/spans/StrikethroughSpan.kt +12 -12
  74. package/android/src/main/java/com/renderrichtext/spans/StrongSpan.kt +39 -39
  75. package/android/src/main/java/com/renderrichtext/spans/TaskListSpan.kt +143 -143
  76. package/android/src/main/java/com/renderrichtext/spans/TextSpan.kt +26 -26
  77. package/android/src/main/java/com/renderrichtext/spans/ThematicBreakSpan.kt +69 -69
  78. package/android/src/main/java/com/renderrichtext/spans/UnorderedListSpan.kt +96 -96
  79. package/android/src/main/java/com/renderrichtext/spoiler/SpoilerStubs.kt +46 -46
  80. package/android/src/main/java/com/renderrichtext/styles/BaseBlockStyle.kt +11 -11
  81. package/android/src/main/java/com/renderrichtext/styles/BlockquoteStyle.kt +83 -83
  82. package/android/src/main/java/com/renderrichtext/styles/CodeBlockStyle.kt +80 -80
  83. package/android/src/main/java/com/renderrichtext/styles/CodeStyle.kt +26 -26
  84. package/android/src/main/java/com/renderrichtext/styles/EmphasisStyle.kt +21 -21
  85. package/android/src/main/java/com/renderrichtext/styles/FactoidStyle.kt +21 -21
  86. package/android/src/main/java/com/renderrichtext/styles/HeadingStyle.kt +33 -33
  87. package/android/src/main/java/com/renderrichtext/styles/HighlightStyle.kt +19 -19
  88. package/android/src/main/java/com/renderrichtext/styles/ImageStyle.kt +29 -29
  89. package/android/src/main/java/com/renderrichtext/styles/InlineImageStyle.kt +17 -17
  90. package/android/src/main/java/com/renderrichtext/styles/InlineMathStyle.kt +17 -17
  91. package/android/src/main/java/com/renderrichtext/styles/LinkPillContent.kt +36 -36
  92. package/android/src/main/java/com/renderrichtext/styles/LinkPillStyle.kt +44 -44
  93. package/android/src/main/java/com/renderrichtext/styles/LinkStyle.kt +23 -23
  94. package/android/src/main/java/com/renderrichtext/styles/LinkVariantEntry.kt +34 -34
  95. package/android/src/main/java/com/renderrichtext/styles/ListStyle.kt +65 -65
  96. package/android/src/main/java/com/renderrichtext/styles/MathStyle.kt +29 -29
  97. package/android/src/main/java/com/renderrichtext/styles/MentionStyle.kt +19 -19
  98. package/android/src/main/java/com/renderrichtext/styles/ParagraphStyle.kt +33 -33
  99. package/android/src/main/java/com/renderrichtext/styles/SpoilerStyle.kt +27 -27
  100. package/android/src/main/java/com/renderrichtext/styles/StrikethroughStyle.kt +17 -17
  101. package/android/src/main/java/com/renderrichtext/styles/StrongStyle.kt +21 -21
  102. package/android/src/main/java/com/renderrichtext/styles/StyleConfig.kt +411 -411
  103. package/android/src/main/java/com/renderrichtext/styles/StyleParser.kt +92 -92
  104. package/android/src/main/java/com/renderrichtext/styles/SubscriptStyle.kt +16 -16
  105. package/android/src/main/java/com/renderrichtext/styles/SuperscriptStyle.kt +16 -16
  106. package/android/src/main/java/com/renderrichtext/styles/TableStyle.kt +75 -75
  107. package/android/src/main/java/com/renderrichtext/styles/TaskListStyle.kt +29 -29
  108. package/android/src/main/java/com/renderrichtext/styles/TextAlignment.kt +32 -32
  109. package/android/src/main/java/com/renderrichtext/styles/ThematicBreakStyle.kt +23 -23
  110. package/android/src/main/java/com/renderrichtext/styles/UnderlineStyle.kt +17 -17
  111. package/android/src/main/java/com/renderrichtext/styles/VideoStyle.kt +31 -31
  112. package/android/src/main/java/com/renderrichtext/utils/common/BorderStyleUtils.kt +86 -86
  113. package/android/src/main/java/com/renderrichtext/utils/common/BreakStrategyUtils.kt +32 -32
  114. package/android/src/main/java/com/renderrichtext/utils/common/CodeBlockHighlighter.kt +36 -36
  115. package/android/src/main/java/com/renderrichtext/utils/common/CodeBlockNode.kt +42 -42
  116. package/android/src/main/java/com/renderrichtext/utils/common/EllipsizeUtils.kt +27 -27
  117. package/android/src/main/java/com/renderrichtext/utils/common/EnrichedTiptapAncestorUtils.kt +10 -10
  118. package/android/src/main/java/com/renderrichtext/utils/common/FeatureFlags.kt +9 -9
  119. package/android/src/main/java/com/renderrichtext/utils/common/LinkRecognitionProps.kt +31 -31
  120. package/android/src/main/java/com/renderrichtext/utils/common/LinkRegexConfig.kt +56 -56
  121. package/android/src/main/java/com/renderrichtext/utils/common/MotionPreferences.kt +18 -18
  122. package/android/src/main/java/com/renderrichtext/utils/common/ReadableMapExtensions.kt +28 -28
  123. package/android/src/main/java/com/renderrichtext/utils/common/layout/RTLUtils.kt +6 -6
  124. package/android/src/main/java/com/renderrichtext/utils/common/serialization/TiptapASTSerializer.kt +368 -368
  125. package/android/src/main/java/com/renderrichtext/utils/text/ImageCache.kt +88 -88
  126. package/android/src/main/java/com/renderrichtext/utils/text/ImageDownloader.kt +203 -203
  127. package/android/src/main/java/com/renderrichtext/utils/text/LocalImageLoader.kt +167 -167
  128. package/android/src/main/java/com/renderrichtext/utils/text/TailFadeInAnimator.kt +84 -84
  129. package/android/src/main/java/com/renderrichtext/utils/text/conversion/HTMLGenerator.kt +1105 -1105
  130. package/android/src/main/java/com/renderrichtext/utils/text/conversion/TiptapExtractor.kt +423 -423
  131. package/android/src/main/java/com/renderrichtext/utils/text/extensions/ParagraphUtils.kt +9 -9
  132. package/android/src/main/java/com/renderrichtext/utils/text/extensions/SpannableExtensions.kt +9 -9
  133. package/android/src/main/java/com/renderrichtext/utils/text/extensions/TextPaintExtensions.kt +76 -76
  134. package/android/src/main/java/com/renderrichtext/utils/text/interaction/CheckboxTouchHelper.kt +61 -61
  135. package/android/src/main/java/com/renderrichtext/utils/text/interaction/TaskListTapUtils.kt +286 -286
  136. package/android/src/main/java/com/renderrichtext/utils/text/span/ApplyLineHeight.kt +39 -39
  137. package/android/src/main/java/com/renderrichtext/utils/text/span/BlockSpacing.kt +142 -142
  138. package/android/src/main/java/com/renderrichtext/utils/text/span/SpanFlags.kt +43 -43
  139. package/android/src/main/java/com/renderrichtext/utils/text/span/WidthAwareSpans.kt +30 -30
  140. package/android/src/main/java/com/renderrichtext/utils/text/view/CodeBlockPressHost.kt +15 -15
  141. package/android/src/main/java/com/renderrichtext/utils/text/view/ImagePressHost.kt +19 -19
  142. package/android/src/main/java/com/renderrichtext/utils/text/view/SelectionActionMode.kt +235 -235
  143. package/android/src/main/java/com/renderrichtext/utils/text/view/TextSelectionColors.kt +48 -48
  144. package/android/src/main/java/com/renderrichtext/utils/text/view/TextViewSetup.kt +119 -119
  145. package/android/src/main/java/com/renderrichtext/views/ContextMenuPopup.kt +217 -217
  146. package/android/src/main/java/com/renderrichtext/views/CopyGlyph.kt +101 -101
  147. package/android/src/main/java/com/renderrichtext/views/WrapGlyph.kt +40 -40
  148. package/android/src/main/jni/CMakeLists.txt +52 -52
  149. package/android/src/main/jni/react/renderer/components/RichTextSpec/ComponentDescriptors.h +30 -30
  150. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextComponentDescriptor.h +29 -29
  151. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextMeasurementManager.cpp +45 -45
  152. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextMeasurementManager.h +21 -21
  153. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextShadowNode.cpp +38 -38
  154. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextShadowNode.h +53 -53
  155. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextState.cpp +9 -9
  156. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextState.h +24 -24
  157. package/android/src/main/jni/react/renderer/components/RichTextSpec/conversions.h +40 -40
  158. package/cpp/parser/TiptapParser.hpp +13 -13
  159. package/cpp/vendor/nlohmann/json.hpp +24765 -24765
  160. package/ios/EnrichedTiptapText.h +27 -27
  161. package/ios/attachments/ENRMImageAttachment.h +51 -51
  162. package/ios/attachments/ENRMImageAttachment.m +398 -398
  163. package/ios/attachments/ENRMLinkPillAttachment.h +47 -47
  164. package/ios/attachments/ENRMLinkPillAttachment.m +324 -324
  165. package/ios/attachments/ENRMLinkPillIconCache.h +28 -28
  166. package/ios/attachments/ENRMLinkPillIconCache.m +181 -181
  167. package/ios/attachments/ENRMLinkPillText.h +27 -27
  168. package/ios/attachments/ENRMLinkPillText.m +118 -118
  169. package/ios/attachments/ENRMMathInlineAttachment.h +7 -7
  170. package/ios/attachments/ENRMMathInlineAttachment.m +4 -4
  171. package/ios/attachments/ThematicBreakAttachment.h +15 -15
  172. package/ios/attachments/ThematicBreakAttachment.m +33 -33
  173. package/ios/internals/ENRMViewFreeMeasurement.h +208 -208
  174. package/ios/internals/EnrichedTiptapTextComponentDescriptor.h +19 -19
  175. package/ios/internals/EnrichedTiptapTextShadowNode.h +43 -43
  176. package/ios/internals/EnrichedTiptapTextShadowNode.mm +69 -69
  177. package/ios/internals/EnrichedTiptapTextState.h +24 -24
  178. package/ios/internals/MeasurementCache.h +308 -308
  179. package/ios/internals/ShadowMeasurementUtils.h +145 -145
  180. package/ios/parser/ENRMTextLinkAttributes.h +7 -7
  181. package/ios/parser/ENRMTiptapParser.h +33 -33
  182. package/ios/parser/ENRMTiptapParser.mm +73 -73
  183. package/ios/parser/TiptapASTNode.m +52 -52
  184. package/ios/renderer/AttributedRenderer.h +23 -23
  185. package/ios/renderer/AttributedRenderer.m +229 -229
  186. package/ios/renderer/BaseRenderer.h +33 -33
  187. package/ios/renderer/BaseRenderer.m +46 -46
  188. package/ios/renderer/BaselineShiftTextAttributes.h +10 -10
  189. package/ios/renderer/BaselineShiftTextAttributes.m +26 -26
  190. package/ios/renderer/BlankLineRenderer.h +4 -4
  191. package/ios/renderer/BlankLineRenderer.m +51 -51
  192. package/ios/renderer/BlockquoteRenderer.h +6 -6
  193. package/ios/renderer/BlockquoteRenderer.m +258 -258
  194. package/ios/renderer/CodeBlockRenderer.h +9 -9
  195. package/ios/renderer/CodeBlockRenderer.m +99 -99
  196. package/ios/renderer/CodeRenderer.h +9 -9
  197. package/ios/renderer/CodeRenderer.m +69 -69
  198. package/ios/renderer/ENRMBlockquoteTextRenderer.h +25 -25
  199. package/ios/renderer/ENRMBlockquoteTextRenderer.m +48 -48
  200. package/ios/renderer/EmphasisRenderer.h +5 -5
  201. package/ios/renderer/EmphasisRenderer.m +99 -99
  202. package/ios/renderer/HeadingRenderer.h +6 -6
  203. package/ios/renderer/HeadingRenderer.m +102 -102
  204. package/ios/renderer/HighlightRenderer.h +7 -7
  205. package/ios/renderer/HighlightRenderer.m +33 -33
  206. package/ios/renderer/LinkRenderer.h +6 -6
  207. package/ios/renderer/LinkRenderer.m +157 -157
  208. package/ios/renderer/ListItemRenderer.h +24 -24
  209. package/ios/renderer/ListItemRenderer.m +283 -283
  210. package/ios/renderer/ListRenderer.h +13 -13
  211. package/ios/renderer/ListRenderer.m +126 -126
  212. package/ios/renderer/MentionRenderer.h +9 -9
  213. package/ios/renderer/MentionRenderer.m +96 -96
  214. package/ios/renderer/NodeRenderer.h +8 -8
  215. package/ios/renderer/ParagraphRenderer.h +6 -6
  216. package/ios/renderer/ParagraphRenderer.m +85 -85
  217. package/ios/renderer/RenderContext.h +145 -145
  218. package/ios/renderer/RenderContext.m +402 -402
  219. package/ios/renderer/RendererFactory.h +12 -12
  220. package/ios/renderer/StrikethroughRenderer.h +5 -5
  221. package/ios/renderer/StrikethroughRenderer.m +30 -30
  222. package/ios/renderer/StrongRenderer.h +5 -5
  223. package/ios/renderer/StrongRenderer.m +87 -87
  224. package/ios/renderer/TextRenderer.h +6 -6
  225. package/ios/renderer/TextRenderer.m +18 -18
  226. package/ios/renderer/ThematicBreakRenderer.h +4 -4
  227. package/ios/renderer/ThematicBreakRenderer.m +41 -41
  228. package/ios/renderer/UnderlineRenderer.h +4 -4
  229. package/ios/renderer/UnderlineRenderer.m +41 -41
  230. package/ios/styles/ENRMFontSlot.h +12 -12
  231. package/ios/styles/ENRMFontSlot.m +24 -24
  232. package/ios/styles/StyleConfig.h +488 -488
  233. package/ios/styles/StyleConfig.mm +2820 -2820
  234. package/ios/utils/AccessibilityInfo.h +35 -35
  235. package/ios/utils/AccessibilityInfo.m +24 -24
  236. package/ios/utils/BlockquoteBorder.h +26 -26
  237. package/ios/utils/BlockquoteBorder.m +294 -294
  238. package/ios/utils/CodeBackground.h +19 -19
  239. package/ios/utils/CodeBackground.m +196 -196
  240. package/ios/utils/CodeBlockBackground.h +17 -17
  241. package/ios/utils/CodeBlockBackground.m +102 -102
  242. package/ios/utils/ContextMenuUtils.h +76 -76
  243. package/ios/utils/ContextMenuUtils.mm +75 -75
  244. package/ios/utils/ENRMAccessibilityLabels.h +40 -40
  245. package/ios/utils/ENRMAccessibilityLabels.m +5 -5
  246. package/ios/utils/ENRMAsyncRenderCoordinator.h +24 -24
  247. package/ios/utils/ENRMAsyncRenderCoordinator.m +37 -37
  248. package/ios/utils/ENRMAtomicSize.h +65 -65
  249. package/ios/utils/ENRMCodeBlockContent.h +37 -37
  250. package/ios/utils/ENRMCodeBlockContent.mm +89 -89
  251. package/ios/utils/ENRMFeatureFlags.h +25 -25
  252. package/ios/utils/ENRMImageDownloader.h +30 -30
  253. package/ios/utils/ENRMImageDownloader.m +168 -168
  254. package/ios/utils/ENRMLatexErrorCoordinator.h +27 -27
  255. package/ios/utils/ENRMLatexErrorCoordinator.mm +75 -75
  256. package/ios/utils/ENRMLatexErrorReporting.h +13 -13
  257. package/ios/utils/ENRMLinkContextMenus.h +93 -93
  258. package/ios/utils/ENRMLinkContextMenus.m +178 -178
  259. package/ios/utils/ENRMLinkRegexConfig.h +30 -30
  260. package/ios/utils/ENRMLinkRegexConfig.mm +104 -104
  261. package/ios/utils/ENRMLinkRegexProps.h +23 -23
  262. package/ios/utils/ENRMLocalImageLoader.h +35 -35
  263. package/ios/utils/ENRMLocalImageLoader.m +111 -111
  264. package/ios/utils/ENRMMenuAction.h +27 -27
  265. package/ios/utils/ENRMMenuAction.m +33 -33
  266. package/ios/utils/ENRMSpoilerOverlayManager.h +15 -15
  267. package/ios/utils/ENRMSpoilerOverlayManager.m +19 -19
  268. package/ios/utils/ENRMSpoilerTapUtils.h +6 -6
  269. package/ios/utils/ENRMSpoilerTapUtils.m +11 -11
  270. package/ios/utils/ENRMTailFadeInAnimator.h +16 -16
  271. package/ios/utils/ENRMTailFadeInAnimator.m +161 -161
  272. package/ios/utils/ENRMTextHitTest.h +23 -23
  273. package/ios/utils/ENRMTextHitTest.m +20 -20
  274. package/ios/utils/ENRMTextInteractionUtils.h +20 -20
  275. package/ios/utils/ENRMTextInteractionUtils.m +15 -15
  276. package/ios/utils/ENRMTextRenderer.h +38 -38
  277. package/ios/utils/ENRMTextRenderer.m +54 -54
  278. package/ios/utils/ENRMTextViewSetup.h +132 -132
  279. package/ios/utils/ENRMUIKit.h +429 -429
  280. package/ios/utils/EditMenuUtils.h +105 -105
  281. package/ios/utils/EditMenuUtils.m +143 -143
  282. package/ios/utils/FontScaleObserver.h +10 -10
  283. package/ios/utils/FontScaleObserver.m +55 -55
  284. package/ios/utils/FontUtils.h +31 -31
  285. package/ios/utils/FontUtils.m +63 -63
  286. package/ios/utils/HTMLGenerator.h +25 -25
  287. package/ios/utils/HTMLGenerator.m +1041 -1041
  288. package/ios/utils/HeightUpdateUtils.h +25 -25
  289. package/ios/utils/ImageRequestHeaderUtils.h +35 -35
  290. package/ios/utils/LastElementUtils.h +97 -97
  291. package/ios/utils/LinkContextMenuUtils.h +55 -55
  292. package/ios/utils/LinkPillContentUtils.h +46 -46
  293. package/ios/utils/ListMarkerDrawer.h +15 -15
  294. package/ios/utils/ListMarkerDrawer.m +239 -239
  295. package/ios/utils/MentionBackground.h +26 -26
  296. package/ios/utils/MentionBackground.m +86 -86
  297. package/ios/utils/ParagraphStyleUtils.h +87 -87
  298. package/ios/utils/ParagraphStyleUtils.m +484 -484
  299. package/ios/utils/PasteboardUtils.h +48 -48
  300. package/ios/utils/PasteboardUtils.m +166 -166
  301. package/ios/utils/RTFExportUtils.h +24 -24
  302. package/ios/utils/RTFExportUtils.m +299 -299
  303. package/ios/utils/SelectionColorUtils.h +14 -14
  304. package/ios/utils/StylePropsUtils.h +1340 -1340
  305. package/ios/utils/TaskListTapUtils.h +6 -6
  306. package/ios/utils/TaskListTapUtils.m +14 -14
  307. package/ios/utils/TextViewLayoutManager.h +14 -14
  308. package/ios/utils/TiptapAccessibilityElementBuilder.h +43 -43
  309. package/ios/utils/TiptapAccessibilityElementBuilder.m +432 -432
  310. package/ios/utils/TiptapExtractor.h +17 -17
  311. package/ios/utils/TiptapExtractor.m +367 -367
  312. package/ios/views/ENRMTiptapTextView.h +25 -25
  313. package/ios/views/ENRMTiptapTextView.m +247 -247
  314. package/package.json +65 -70
  315. package/react-native.config.js +10 -10
  316. package/src/admonitionDefaults.ts +58 -58
  317. package/src/darkModeTextStyle.ts +99 -99
  318. package/src/headingDefaults.ts +21 -21
  319. package/src/index.tsx +4 -4
  320. package/src/linkVariantUtils.ts +120 -120
  321. package/src/normalizeTiptapStyle.ts +397 -397
  322. package/src/rn-globals.d.ts +1 -1
  323. package/src/styleUtils.ts +107 -107
  324. package/src/types/TiptapStyle.ts +532 -532
  325. package/src/types/TiptapStyleInternal.ts +299 -299
  326. package/src/types/linkPill.ts +9 -9
  327. package/src/utils/isDev.ts +4 -4
@@ -1,398 +1,398 @@
1
- #import "ENRMImageAttachment.h"
2
- #import "ENRMImageDownloader.h"
3
- #import "ENRMUIKit.h"
4
- #import "RuntimeKeys.h"
5
- #import "StyleConfig.h"
6
- #import <objc/runtime.h>
7
-
8
- #define CACHE_KEY_PROCESSED(url, w, h, r, m) [NSString stringWithFormat:@"%@_w%.1f_h%.1f_r%.1f_m%@", url, w, h, r, m]
9
-
10
- static NSCache<NSString *, RCTUIImage *> *_originalImageCache;
11
- static NSCache<NSString *, RCTUIImage *> *_processedImageCache;
12
-
13
- @implementation RCTUIView (ENRMImageLayoutObserver)
14
-
15
- - (id<ENRMImageLayoutObserver>)enrm_imageLayoutObserver
16
- {
17
- RCTUIView *view = self;
18
- while (view && ![view conformsToProtocol:@protocol(ENRMImageLayoutObserver)]) {
19
- view = view.superview;
20
- }
21
- return (id<ENRMImageLayoutObserver>)view;
22
- }
23
-
24
- @end
25
-
26
- @interface ENRMImageAttachment ()
27
-
28
- @property (nonatomic, copy) NSString *imageURL;
29
- @property (nonatomic, copy, nullable) NSDictionary<NSString *, NSString *> *requestHeaders;
30
- @property (nonatomic, copy) NSString *cacheKey;
31
- @property (nonatomic, assign) BOOL isInline;
32
- @property (nonatomic, assign) CGFloat cachedHeight;
33
- @property (nonatomic, assign) CGFloat cachedMaxHeight;
34
- @property (nonatomic, assign) CGFloat cachedAspectRatio;
35
- @property (nonatomic, copy) NSString *cachedResizeMode;
36
- @property (nonatomic, assign) CGFloat cachedBorderRadius;
37
- @property (nonatomic, weak) NSTextContainer *textContainer;
38
- @property (nonatomic, weak) ENRMPlatformTextView *textView;
39
- @property (nonatomic, strong) RCTUIImage *originalImage;
40
- @property (nonatomic, strong) RCTUIImage *loadedImage;
41
- @property (nonatomic, strong) RCTUIImage *placeholderImage;
42
- @property (nonatomic, copy) NSString *lastProcessedKey;
43
-
44
- @end
45
-
46
- @implementation ENRMImageAttachment
47
-
48
- + (NSCache<NSString *, RCTUIImage *> *)originalImageCache
49
- {
50
- static dispatch_once_t onceToken;
51
- dispatch_once(&onceToken, ^{
52
- _originalImageCache = [[NSCache alloc] init];
53
- _originalImageCache.countLimit = 50;
54
- _originalImageCache.totalCostLimit = 1024 * 1024 * 20; // 20 MB
55
- });
56
- return _originalImageCache;
57
- }
58
-
59
- + (NSCache<NSString *, RCTUIImage *> *)processedImageCache
60
- {
61
- static dispatch_once_t onceToken;
62
- dispatch_once(&onceToken, ^{
63
- _processedImageCache = [[NSCache alloc] init];
64
- _processedImageCache.countLimit = 100;
65
- _processedImageCache.totalCostLimit = 1024 * 1024 * 30; // 30 MB
66
- });
67
- return _processedImageCache;
68
- }
69
-
70
- + (instancetype)attachmentForURL:(NSString *)imageURL config:(StyleConfig *)config isInline:(BOOL)isInline
71
- {
72
- // Always a fresh instance, never shared across positions: the table renderer can draw
73
- // the same image URL at a different width than a copy outside the table, and a shared
74
- // NSTextAttachment would thrash its single last-processed width into a redraw loop. The
75
- // image caches keep fresh instances cheap (no re-fetch or re-scale).
76
- return [[self alloc] initWithImageURL:imageURL config:config isInline:isInline];
77
- }
78
-
79
- - (instancetype)initWithImageURL:(NSString *)imageURL config:(StyleConfig *)config isInline:(BOOL)isInline
80
- {
81
- self = [super init];
82
- if (self) {
83
- _imageURL = imageURL;
84
- _requestHeaders = [[config imageRequestHeaders] copy];
85
- _cacheKey = ENRMImageCacheKey(imageURL, _requestHeaders);
86
- _isInline = isInline;
87
-
88
- _cachedHeight = isInline ? [config inlineImageSize] : [config imageHeight];
89
- _cachedMaxHeight = [config imageMaxHeight];
90
- _cachedAspectRatio = [config imageAspectRatio];
91
- _cachedResizeMode = [config imageResizeMode];
92
- _cachedBorderRadius = [config imageBorderRadius];
93
-
94
- [self setupPlaceholder];
95
- [self startDownloadingImage];
96
- }
97
- return self;
98
- }
99
-
100
- - (CGFloat)resolvedBoxHeightForWidth:(CGFloat)width
101
- {
102
- if (self.cachedAspectRatio > 0 && width > 0) {
103
- return width / self.cachedAspectRatio;
104
- }
105
-
106
- if (self.cachedMaxHeight > 0) {
107
- RCTUIImage *source = self.originalImage;
108
- if (source && width > 0 && source.size.width > 0 && source.size.height > 0) {
109
- CGFloat fitted = width * source.size.height / source.size.width;
110
- return MIN(self.cachedMaxHeight, fitted);
111
- }
112
- // Intrinsic size not known yet — fall back to the full max height; the box
113
- // shrinks once the image loads and layout is invalidated.
114
- return self.cachedMaxHeight;
115
- }
116
-
117
- return self.cachedHeight;
118
- }
119
-
120
- // True when resizeMode is unset ('') - legacy always implies a fixed height box.
121
-
122
- - (BOOL)isLegacyBlockSizing
123
- {
124
- return !self.isInline && self.cachedResizeMode.length == 0;
125
- }
126
-
127
- - (CGRect)attachmentBoundsForTextContainer:(NSTextContainer *)textContainer
128
- proposedLineFragment:(CGRect)lineFragment
129
- glyphPosition:(CGPoint)position
130
- characterIndex:(NSUInteger)characterIndex
131
- {
132
- CGFloat height = self.cachedHeight;
133
- CGFloat width = self.isInline ? height : (lineFragment.size.width > 0 ? lineFragment.size.width : height);
134
-
135
- if (!self.isInline) {
136
- height = [self resolvedBoxHeightForWidth:width];
137
- return CGRectMake(0, 0, width, height);
138
- }
139
-
140
- UIFont *appliedFont = nil;
141
- NSLayoutManager *layoutManager = textContainer.layoutManager;
142
- NSTextStorage *textStorage = layoutManager.textStorage;
143
-
144
- if (textStorage && characterIndex < textStorage.length) {
145
- appliedFont = [textStorage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL];
146
- }
147
-
148
- CGFloat verticalOffset;
149
- if (appliedFont) {
150
- verticalOffset = (appliedFont.capHeight - height) / 2.0;
151
- } else {
152
- verticalOffset = (lineFragment.size.height - height) / 2.0;
153
- }
154
-
155
- return CGRectMake(0, verticalOffset, width, height);
156
- }
157
-
158
- - (RCTUIImage *)imageForBounds:(CGRect)imageBounds
159
- textContainer:(NSTextContainer *)textContainer
160
- characterIndex:(NSUInteger)characterIndex
161
- {
162
- self.textContainer = textContainer;
163
-
164
- if (self.originalImage && imageBounds.size.width > 0) {
165
- self.bounds = imageBounds;
166
- [self processAndApplyImage:self.originalImage withTargetWidth:imageBounds.size.width];
167
- }
168
-
169
- return self.loadedImage ?: self.placeholderImage;
170
- }
171
-
172
- - (void)handleLoadedImage:(RCTUIImage *)image
173
- {
174
- if (!image)
175
- return;
176
-
177
- // The downloader completes synchronously on cache hits, which can happen on the render queue
178
- if (!NSThread.isMainThread) {
179
- dispatch_async(dispatch_get_main_queue(), ^{ [self handleLoadedImage:image]; });
180
- return;
181
- }
182
-
183
- self.originalImage = image;
184
- // UIKit reads the plain image property for save/drag/copy — it must hold the original
185
- self.image = image;
186
- CGFloat targetWidth = self.isInline ? self.cachedHeight : self.bounds.size.width;
187
-
188
- // Defer processing if we don't have valid bounds yet (common for non-inline block images)
189
- if (!self.isInline && targetWidth <= 0) {
190
- return;
191
- }
192
-
193
- [self processAndApplyImage:image withTargetWidth:targetWidth];
194
- }
195
-
196
- - (void)processAndApplyImage:(RCTUIImage *)image withTargetWidth:(CGFloat)targetWidth
197
- {
198
- if (targetWidth <= 0)
199
- return;
200
-
201
- CGFloat boxHeight = self.isInline ? self.cachedHeight : [self resolvedBoxHeightForWidth:targetWidth];
202
-
203
- NSString *processedKey =
204
- CACHE_KEY_PROCESSED(self.cacheKey, targetWidth, boxHeight, self.cachedBorderRadius, self.cachedResizeMode);
205
-
206
- if ([processedKey isEqualToString:self.lastProcessedKey])
207
- return;
208
- self.lastProcessedKey = processedKey;
209
-
210
- RCTUIImage *cachedProcessed = [[ENRMImageAttachment processedImageCache] objectForKey:processedKey];
211
-
212
- if (cachedProcessed) {
213
- self.loadedImage = cachedProcessed;
214
- [self refreshDisplay];
215
- return;
216
- }
217
-
218
- __weak typeof(self) weakSelf = self;
219
- dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
220
- __strong typeof(weakSelf) strongSelf = weakSelf;
221
- if (!strongSelf)
222
- return;
223
-
224
- RCTUIImage *processedImage = [strongSelf createScaledImage:image
225
- toWidth:targetWidth
226
- height:boxHeight
227
- borderRadius:strongSelf.cachedBorderRadius];
228
-
229
- if (processedImage) {
230
- [[ENRMImageAttachment processedImageCache] setObject:processedImage
231
- forKey:processedKey
232
- cost:ENRMImageByteCost(processedImage)];
233
- }
234
-
235
- dispatch_async(dispatch_get_main_queue(), ^{
236
- strongSelf.loadedImage = processedImage;
237
- if (strongSelf.isInline) {
238
- strongSelf.bounds = CGRectMake(0, 0, strongSelf.cachedHeight, strongSelf.cachedHeight);
239
- }
240
- [strongSelf refreshDisplay];
241
- });
242
- });
243
- }
244
-
245
- - (RCTUIImage *)createScaledImage:(RCTUIImage *)image
246
- toWidth:(CGFloat)targetWidth
247
- height:(CGFloat)targetHeight
248
- borderRadius:(CGFloat)radius
249
- {
250
- CGFloat sourceWidth = image.size.width;
251
- CGFloat sourceHeight = image.size.height;
252
- if (sourceWidth <= 0 || sourceHeight <= 0)
253
- return nil;
254
-
255
- CGSize source = CGSizeMake(sourceWidth, sourceHeight);
256
- CGSize box = CGSizeMake(targetWidth, targetHeight);
257
- BOOL legacy = [self isLegacyBlockSizing];
258
-
259
- CGRect drawingRect;
260
- if (self.isInline || legacy) {
261
- CGFloat drawingWidth, drawingHeight;
262
- if (!self.isInline) {
263
- CGFloat aspectRatioScale = targetWidth / sourceWidth;
264
- drawingWidth = targetWidth;
265
- drawingHeight = sourceHeight * aspectRatioScale;
266
- } else {
267
- drawingWidth = targetWidth;
268
- drawingHeight = targetHeight;
269
- }
270
- drawingRect = CGRectMake((targetWidth - drawingWidth) / 2.0, (targetHeight - drawingHeight) / 2.0, drawingWidth,
271
- drawingHeight);
272
- } else {
273
- drawingRect = [self drawingRectForResizeMode:self.cachedResizeMode source:source box:box];
274
- }
275
-
276
- RCTUIGraphicsImageRenderer *renderer = ImageRendererForSize(box);
277
-
278
- return [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *context) {
279
- if (radius > 0) {
280
- CGRect clipRect = CGRectIntersection(CGRectMake(0, 0, targetWidth, targetHeight), drawingRect);
281
- UIBezierPath *path = UIBezierPathWithRoundedRect(clipRect, radius);
282
- [path addClip];
283
- }
284
- [image drawInRect:drawingRect];
285
- }];
286
- }
287
-
288
- - (CGRect)drawingRectForResizeMode:(NSString *)mode source:(CGSize)source box:(CGSize)box
289
- {
290
- if ([mode isEqualToString:@"stretch"]) {
291
- return CGRectMake(0, 0, box.width, box.height);
292
- }
293
-
294
- CGFloat widthScale = box.width / source.width;
295
- CGFloat heightScale = box.height / source.height;
296
-
297
- CGFloat scale;
298
- if ([mode isEqualToString:@"contain"]) {
299
- scale = MIN(widthScale, heightScale);
300
- } else if ([mode isEqualToString:@"center"]) {
301
- scale = MIN(1.0, MIN(widthScale, heightScale));
302
- } else if ([mode isEqualToString:@"none"]) {
303
- scale = 1.0;
304
- } else { // cover (default)
305
- scale = MAX(widthScale, heightScale);
306
- }
307
-
308
- CGFloat drawingWidth = source.width * scale;
309
- CGFloat drawingHeight = source.height * scale;
310
- return CGRectMake((box.width - drawingWidth) / 2.0, (box.height - drawingHeight) / 2.0, drawingWidth, drawingHeight);
311
- }
312
-
313
- - (void)startDownloadingImage
314
- {
315
- if (self.imageURL.length == 0)
316
- return;
317
-
318
- __weak typeof(self) weakSelf = self;
319
- [[ENRMImageDownloader shared] downloadURL:self.imageURL
320
- headers:self.requestHeaders
321
- completion:^(RCTUIImage *image) { [weakSelf handleLoadedImage:image]; }];
322
- }
323
-
324
- - (void)refreshDisplay
325
- {
326
- // Notify self-drawing hosts (e.g. a table grid) that rasterize us via
327
- // -drawWithRect: and have no live text view for the invalidation below to reach.
328
- if (self.onImageLoaded) {
329
- self.onImageLoaded();
330
- }
331
-
332
- UITextView *textView = [self fetchAssociatedTextView];
333
- if (!textView)
334
- return;
335
-
336
- NSRange range = [self findAttachmentRangeInText:textView.textStorage];
337
- if (range.location != NSNotFound) {
338
- [textView.layoutManager invalidateDisplayForCharacterRange:range];
339
- if (!self.isInline) {
340
- [textView.layoutManager invalidateLayoutForCharacterRange:range actualCharacterRange:NULL];
341
- [self notifyImageLayoutObserver:textView];
342
- }
343
- }
344
- }
345
-
346
- // With maxHeight/aspectRatio sizing the box height can settle after the image
347
- // loads, while the component was measured (and its size cached) with the
348
- // pre-load fallback. Notify the hosting component so it can re-measure.
349
- // Dispatched async: refreshDisplay can run mid-layout (imageForBounds) and
350
- // re-measuring would re-enter the layout manager.
351
- - (void)notifyImageLayoutObserver:(UITextView *)textView
352
- {
353
- // Only maxHeight/aspectRatio boxes can change height after load; a fixed
354
- // height box (legacy or explicit resizeMode) never needs a re-measure.
355
- if (self.cachedMaxHeight <= 0 && self.cachedAspectRatio <= 0)
356
- return;
357
-
358
- id<ENRMImageLayoutObserver> observer = [textView enrm_imageLayoutObserver];
359
- if (!observer)
360
- return;
361
-
362
- dispatch_async(dispatch_get_main_queue(), ^{ [observer imageAttachmentDidResolveLayout]; });
363
- }
364
-
365
- - (ENRMPlatformTextView *)fetchAssociatedTextView
366
- {
367
- if (self.textView)
368
- return self.textView;
369
- if (!self.textContainer)
370
- return nil;
371
- self.textView = objc_getAssociatedObject(self.textContainer, kTextViewKey);
372
- return self.textView;
373
- }
374
-
375
- - (void)setupPlaceholder
376
- {
377
- CGFloat size = self.cachedHeight;
378
- self.bounds = CGRectMake(0, 0, size, size);
379
- RCTUIGraphicsImageRenderer *renderer = [[RCTUIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(1, 1)];
380
- self.placeholderImage = [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *ctx){}];
381
- }
382
-
383
- - (NSRange)findAttachmentRangeInText:(NSAttributedString *)attributedString
384
- {
385
- __block NSRange foundRange = NSMakeRange(NSNotFound, 0);
386
- [attributedString enumerateAttribute:NSAttachmentAttributeName
387
- inRange:NSMakeRange(0, attributedString.length)
388
- options:0
389
- usingBlock:^(id value, NSRange range, BOOL *stop) {
390
- if (value == self) {
391
- foundRange = range;
392
- *stop = YES;
393
- }
394
- }];
395
- return foundRange;
396
- }
397
-
398
- @end
1
+ #import "ENRMImageAttachment.h"
2
+ #import "ENRMImageDownloader.h"
3
+ #import "ENRMUIKit.h"
4
+ #import "RuntimeKeys.h"
5
+ #import "StyleConfig.h"
6
+ #import <objc/runtime.h>
7
+
8
+ #define CACHE_KEY_PROCESSED(url, w, h, r, m) [NSString stringWithFormat:@"%@_w%.1f_h%.1f_r%.1f_m%@", url, w, h, r, m]
9
+
10
+ static NSCache<NSString *, RCTUIImage *> *_originalImageCache;
11
+ static NSCache<NSString *, RCTUIImage *> *_processedImageCache;
12
+
13
+ @implementation RCTUIView (ENRMImageLayoutObserver)
14
+
15
+ - (id<ENRMImageLayoutObserver>)enrm_imageLayoutObserver
16
+ {
17
+ RCTUIView *view = self;
18
+ while (view && ![view conformsToProtocol:@protocol(ENRMImageLayoutObserver)]) {
19
+ view = view.superview;
20
+ }
21
+ return (id<ENRMImageLayoutObserver>)view;
22
+ }
23
+
24
+ @end
25
+
26
+ @interface ENRMImageAttachment ()
27
+
28
+ @property (nonatomic, copy) NSString *imageURL;
29
+ @property (nonatomic, copy, nullable) NSDictionary<NSString *, NSString *> *requestHeaders;
30
+ @property (nonatomic, copy) NSString *cacheKey;
31
+ @property (nonatomic, assign) BOOL isInline;
32
+ @property (nonatomic, assign) CGFloat cachedHeight;
33
+ @property (nonatomic, assign) CGFloat cachedMaxHeight;
34
+ @property (nonatomic, assign) CGFloat cachedAspectRatio;
35
+ @property (nonatomic, copy) NSString *cachedResizeMode;
36
+ @property (nonatomic, assign) CGFloat cachedBorderRadius;
37
+ @property (nonatomic, weak) NSTextContainer *textContainer;
38
+ @property (nonatomic, weak) ENRMPlatformTextView *textView;
39
+ @property (nonatomic, strong) RCTUIImage *originalImage;
40
+ @property (nonatomic, strong) RCTUIImage *loadedImage;
41
+ @property (nonatomic, strong) RCTUIImage *placeholderImage;
42
+ @property (nonatomic, copy) NSString *lastProcessedKey;
43
+
44
+ @end
45
+
46
+ @implementation ENRMImageAttachment
47
+
48
+ + (NSCache<NSString *, RCTUIImage *> *)originalImageCache
49
+ {
50
+ static dispatch_once_t onceToken;
51
+ dispatch_once(&onceToken, ^{
52
+ _originalImageCache = [[NSCache alloc] init];
53
+ _originalImageCache.countLimit = 50;
54
+ _originalImageCache.totalCostLimit = 1024 * 1024 * 20; // 20 MB
55
+ });
56
+ return _originalImageCache;
57
+ }
58
+
59
+ + (NSCache<NSString *, RCTUIImage *> *)processedImageCache
60
+ {
61
+ static dispatch_once_t onceToken;
62
+ dispatch_once(&onceToken, ^{
63
+ _processedImageCache = [[NSCache alloc] init];
64
+ _processedImageCache.countLimit = 100;
65
+ _processedImageCache.totalCostLimit = 1024 * 1024 * 30; // 30 MB
66
+ });
67
+ return _processedImageCache;
68
+ }
69
+
70
+ + (instancetype)attachmentForURL:(NSString *)imageURL config:(StyleConfig *)config isInline:(BOOL)isInline
71
+ {
72
+ // Always a fresh instance, never shared across positions: the table renderer can draw
73
+ // the same image URL at a different width than a copy outside the table, and a shared
74
+ // NSTextAttachment would thrash its single last-processed width into a redraw loop. The
75
+ // image caches keep fresh instances cheap (no re-fetch or re-scale).
76
+ return [[self alloc] initWithImageURL:imageURL config:config isInline:isInline];
77
+ }
78
+
79
+ - (instancetype)initWithImageURL:(NSString *)imageURL config:(StyleConfig *)config isInline:(BOOL)isInline
80
+ {
81
+ self = [super init];
82
+ if (self) {
83
+ _imageURL = imageURL;
84
+ _requestHeaders = [[config imageRequestHeaders] copy];
85
+ _cacheKey = ENRMImageCacheKey(imageURL, _requestHeaders);
86
+ _isInline = isInline;
87
+
88
+ _cachedHeight = isInline ? [config inlineImageSize] : [config imageHeight];
89
+ _cachedMaxHeight = [config imageMaxHeight];
90
+ _cachedAspectRatio = [config imageAspectRatio];
91
+ _cachedResizeMode = [config imageResizeMode];
92
+ _cachedBorderRadius = [config imageBorderRadius];
93
+
94
+ [self setupPlaceholder];
95
+ [self startDownloadingImage];
96
+ }
97
+ return self;
98
+ }
99
+
100
+ - (CGFloat)resolvedBoxHeightForWidth:(CGFloat)width
101
+ {
102
+ if (self.cachedAspectRatio > 0 && width > 0) {
103
+ return width / self.cachedAspectRatio;
104
+ }
105
+
106
+ if (self.cachedMaxHeight > 0) {
107
+ RCTUIImage *source = self.originalImage;
108
+ if (source && width > 0 && source.size.width > 0 && source.size.height > 0) {
109
+ CGFloat fitted = width * source.size.height / source.size.width;
110
+ return MIN(self.cachedMaxHeight, fitted);
111
+ }
112
+ // Intrinsic size not known yet — fall back to the full max height; the box
113
+ // shrinks once the image loads and layout is invalidated.
114
+ return self.cachedMaxHeight;
115
+ }
116
+
117
+ return self.cachedHeight;
118
+ }
119
+
120
+ // True when resizeMode is unset ('') - legacy always implies a fixed height box.
121
+
122
+ - (BOOL)isLegacyBlockSizing
123
+ {
124
+ return !self.isInline && self.cachedResizeMode.length == 0;
125
+ }
126
+
127
+ - (CGRect)attachmentBoundsForTextContainer:(NSTextContainer *)textContainer
128
+ proposedLineFragment:(CGRect)lineFragment
129
+ glyphPosition:(CGPoint)position
130
+ characterIndex:(NSUInteger)characterIndex
131
+ {
132
+ CGFloat height = self.cachedHeight;
133
+ CGFloat width = self.isInline ? height : (lineFragment.size.width > 0 ? lineFragment.size.width : height);
134
+
135
+ if (!self.isInline) {
136
+ height = [self resolvedBoxHeightForWidth:width];
137
+ return CGRectMake(0, 0, width, height);
138
+ }
139
+
140
+ UIFont *appliedFont = nil;
141
+ NSLayoutManager *layoutManager = textContainer.layoutManager;
142
+ NSTextStorage *textStorage = layoutManager.textStorage;
143
+
144
+ if (textStorage && characterIndex < textStorage.length) {
145
+ appliedFont = [textStorage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL];
146
+ }
147
+
148
+ CGFloat verticalOffset;
149
+ if (appliedFont) {
150
+ verticalOffset = (appliedFont.capHeight - height) / 2.0;
151
+ } else {
152
+ verticalOffset = (lineFragment.size.height - height) / 2.0;
153
+ }
154
+
155
+ return CGRectMake(0, verticalOffset, width, height);
156
+ }
157
+
158
+ - (RCTUIImage *)imageForBounds:(CGRect)imageBounds
159
+ textContainer:(NSTextContainer *)textContainer
160
+ characterIndex:(NSUInteger)characterIndex
161
+ {
162
+ self.textContainer = textContainer;
163
+
164
+ if (self.originalImage && imageBounds.size.width > 0) {
165
+ self.bounds = imageBounds;
166
+ [self processAndApplyImage:self.originalImage withTargetWidth:imageBounds.size.width];
167
+ }
168
+
169
+ return self.loadedImage ?: self.placeholderImage;
170
+ }
171
+
172
+ - (void)handleLoadedImage:(RCTUIImage *)image
173
+ {
174
+ if (!image)
175
+ return;
176
+
177
+ // The downloader completes synchronously on cache hits, which can happen on the render queue
178
+ if (!NSThread.isMainThread) {
179
+ dispatch_async(dispatch_get_main_queue(), ^{ [self handleLoadedImage:image]; });
180
+ return;
181
+ }
182
+
183
+ self.originalImage = image;
184
+ // UIKit reads the plain image property for save/drag/copy — it must hold the original
185
+ self.image = image;
186
+ CGFloat targetWidth = self.isInline ? self.cachedHeight : self.bounds.size.width;
187
+
188
+ // Defer processing if we don't have valid bounds yet (common for non-inline block images)
189
+ if (!self.isInline && targetWidth <= 0) {
190
+ return;
191
+ }
192
+
193
+ [self processAndApplyImage:image withTargetWidth:targetWidth];
194
+ }
195
+
196
+ - (void)processAndApplyImage:(RCTUIImage *)image withTargetWidth:(CGFloat)targetWidth
197
+ {
198
+ if (targetWidth <= 0)
199
+ return;
200
+
201
+ CGFloat boxHeight = self.isInline ? self.cachedHeight : [self resolvedBoxHeightForWidth:targetWidth];
202
+
203
+ NSString *processedKey =
204
+ CACHE_KEY_PROCESSED(self.cacheKey, targetWidth, boxHeight, self.cachedBorderRadius, self.cachedResizeMode);
205
+
206
+ if ([processedKey isEqualToString:self.lastProcessedKey])
207
+ return;
208
+ self.lastProcessedKey = processedKey;
209
+
210
+ RCTUIImage *cachedProcessed = [[ENRMImageAttachment processedImageCache] objectForKey:processedKey];
211
+
212
+ if (cachedProcessed) {
213
+ self.loadedImage = cachedProcessed;
214
+ [self refreshDisplay];
215
+ return;
216
+ }
217
+
218
+ __weak typeof(self) weakSelf = self;
219
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
220
+ __strong typeof(weakSelf) strongSelf = weakSelf;
221
+ if (!strongSelf)
222
+ return;
223
+
224
+ RCTUIImage *processedImage = [strongSelf createScaledImage:image
225
+ toWidth:targetWidth
226
+ height:boxHeight
227
+ borderRadius:strongSelf.cachedBorderRadius];
228
+
229
+ if (processedImage) {
230
+ [[ENRMImageAttachment processedImageCache] setObject:processedImage
231
+ forKey:processedKey
232
+ cost:ENRMImageByteCost(processedImage)];
233
+ }
234
+
235
+ dispatch_async(dispatch_get_main_queue(), ^{
236
+ strongSelf.loadedImage = processedImage;
237
+ if (strongSelf.isInline) {
238
+ strongSelf.bounds = CGRectMake(0, 0, strongSelf.cachedHeight, strongSelf.cachedHeight);
239
+ }
240
+ [strongSelf refreshDisplay];
241
+ });
242
+ });
243
+ }
244
+
245
+ - (RCTUIImage *)createScaledImage:(RCTUIImage *)image
246
+ toWidth:(CGFloat)targetWidth
247
+ height:(CGFloat)targetHeight
248
+ borderRadius:(CGFloat)radius
249
+ {
250
+ CGFloat sourceWidth = image.size.width;
251
+ CGFloat sourceHeight = image.size.height;
252
+ if (sourceWidth <= 0 || sourceHeight <= 0)
253
+ return nil;
254
+
255
+ CGSize source = CGSizeMake(sourceWidth, sourceHeight);
256
+ CGSize box = CGSizeMake(targetWidth, targetHeight);
257
+ BOOL legacy = [self isLegacyBlockSizing];
258
+
259
+ CGRect drawingRect;
260
+ if (self.isInline || legacy) {
261
+ CGFloat drawingWidth, drawingHeight;
262
+ if (!self.isInline) {
263
+ CGFloat aspectRatioScale = targetWidth / sourceWidth;
264
+ drawingWidth = targetWidth;
265
+ drawingHeight = sourceHeight * aspectRatioScale;
266
+ } else {
267
+ drawingWidth = targetWidth;
268
+ drawingHeight = targetHeight;
269
+ }
270
+ drawingRect = CGRectMake((targetWidth - drawingWidth) / 2.0, (targetHeight - drawingHeight) / 2.0, drawingWidth,
271
+ drawingHeight);
272
+ } else {
273
+ drawingRect = [self drawingRectForResizeMode:self.cachedResizeMode source:source box:box];
274
+ }
275
+
276
+ RCTUIGraphicsImageRenderer *renderer = ImageRendererForSize(box);
277
+
278
+ return [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *context) {
279
+ if (radius > 0) {
280
+ CGRect clipRect = CGRectIntersection(CGRectMake(0, 0, targetWidth, targetHeight), drawingRect);
281
+ UIBezierPath *path = UIBezierPathWithRoundedRect(clipRect, radius);
282
+ [path addClip];
283
+ }
284
+ [image drawInRect:drawingRect];
285
+ }];
286
+ }
287
+
288
+ - (CGRect)drawingRectForResizeMode:(NSString *)mode source:(CGSize)source box:(CGSize)box
289
+ {
290
+ if ([mode isEqualToString:@"stretch"]) {
291
+ return CGRectMake(0, 0, box.width, box.height);
292
+ }
293
+
294
+ CGFloat widthScale = box.width / source.width;
295
+ CGFloat heightScale = box.height / source.height;
296
+
297
+ CGFloat scale;
298
+ if ([mode isEqualToString:@"contain"]) {
299
+ scale = MIN(widthScale, heightScale);
300
+ } else if ([mode isEqualToString:@"center"]) {
301
+ scale = MIN(1.0, MIN(widthScale, heightScale));
302
+ } else if ([mode isEqualToString:@"none"]) {
303
+ scale = 1.0;
304
+ } else { // cover (default)
305
+ scale = MAX(widthScale, heightScale);
306
+ }
307
+
308
+ CGFloat drawingWidth = source.width * scale;
309
+ CGFloat drawingHeight = source.height * scale;
310
+ return CGRectMake((box.width - drawingWidth) / 2.0, (box.height - drawingHeight) / 2.0, drawingWidth, drawingHeight);
311
+ }
312
+
313
+ - (void)startDownloadingImage
314
+ {
315
+ if (self.imageURL.length == 0)
316
+ return;
317
+
318
+ __weak typeof(self) weakSelf = self;
319
+ [[ENRMImageDownloader shared] downloadURL:self.imageURL
320
+ headers:self.requestHeaders
321
+ completion:^(RCTUIImage *image) { [weakSelf handleLoadedImage:image]; }];
322
+ }
323
+
324
+ - (void)refreshDisplay
325
+ {
326
+ // Notify self-drawing hosts (e.g. a table grid) that rasterize us via
327
+ // -drawWithRect: and have no live text view for the invalidation below to reach.
328
+ if (self.onImageLoaded) {
329
+ self.onImageLoaded();
330
+ }
331
+
332
+ UITextView *textView = [self fetchAssociatedTextView];
333
+ if (!textView)
334
+ return;
335
+
336
+ NSRange range = [self findAttachmentRangeInText:textView.textStorage];
337
+ if (range.location != NSNotFound) {
338
+ [textView.layoutManager invalidateDisplayForCharacterRange:range];
339
+ if (!self.isInline) {
340
+ [textView.layoutManager invalidateLayoutForCharacterRange:range actualCharacterRange:NULL];
341
+ [self notifyImageLayoutObserver:textView];
342
+ }
343
+ }
344
+ }
345
+
346
+ // With maxHeight/aspectRatio sizing the box height can settle after the image
347
+ // loads, while the component was measured (and its size cached) with the
348
+ // pre-load fallback. Notify the hosting component so it can re-measure.
349
+ // Dispatched async: refreshDisplay can run mid-layout (imageForBounds) and
350
+ // re-measuring would re-enter the layout manager.
351
+ - (void)notifyImageLayoutObserver:(UITextView *)textView
352
+ {
353
+ // Only maxHeight/aspectRatio boxes can change height after load; a fixed
354
+ // height box (legacy or explicit resizeMode) never needs a re-measure.
355
+ if (self.cachedMaxHeight <= 0 && self.cachedAspectRatio <= 0)
356
+ return;
357
+
358
+ id<ENRMImageLayoutObserver> observer = [textView enrm_imageLayoutObserver];
359
+ if (!observer)
360
+ return;
361
+
362
+ dispatch_async(dispatch_get_main_queue(), ^{ [observer imageAttachmentDidResolveLayout]; });
363
+ }
364
+
365
+ - (ENRMPlatformTextView *)fetchAssociatedTextView
366
+ {
367
+ if (self.textView)
368
+ return self.textView;
369
+ if (!self.textContainer)
370
+ return nil;
371
+ self.textView = objc_getAssociatedObject(self.textContainer, kTextViewKey);
372
+ return self.textView;
373
+ }
374
+
375
+ - (void)setupPlaceholder
376
+ {
377
+ CGFloat size = self.cachedHeight;
378
+ self.bounds = CGRectMake(0, 0, size, size);
379
+ RCTUIGraphicsImageRenderer *renderer = [[RCTUIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(1, 1)];
380
+ self.placeholderImage = [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *ctx){}];
381
+ }
382
+
383
+ - (NSRange)findAttachmentRangeInText:(NSAttributedString *)attributedString
384
+ {
385
+ __block NSRange foundRange = NSMakeRange(NSNotFound, 0);
386
+ [attributedString enumerateAttribute:NSAttachmentAttributeName
387
+ inRange:NSMakeRange(0, attributedString.length)
388
+ options:0
389
+ usingBlock:^(id value, NSRange range, BOOL *stop) {
390
+ if (value == self) {
391
+ foundRange = range;
392
+ *stop = YES;
393
+ }
394
+ }];
395
+ return foundRange;
396
+ }
397
+
398
+ @end