react-native-enriched-markdown-scaffold 0.7.4-scaffold.8 → 1.0.2-scaffold.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/README.md +54 -8
  2. package/ReactNativeEnrichedMarkdown.podspec +82 -73
  3. package/android/build.gradle +191 -1
  4. package/android/consumer-rules.pro +1 -1
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +9 -0
  6. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +3 -0
  7. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerDelegate.java +27 -0
  8. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerInterface.java +9 -0
  9. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +9 -0
  10. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +3 -0
  11. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +75 -0
  12. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +63 -0
  13. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +40 -0
  14. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +839 -23
  15. package/android/src/main/baseline-prof.txt +1 -1
  16. package/android/src/main/cpp/jni-adapter.cpp +74 -0
  17. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +175 -11
  18. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +17 -0
  19. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +50 -0
  20. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +48 -1
  21. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +10 -1
  22. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +42 -0
  23. package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +82 -4
  24. package/android/src/main/java/com/swmansion/enriched/markdown/events/CopyPressEvent.kt +24 -0
  25. package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputManager.kt +62 -4
  26. package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +911 -460
  27. package/android/src/main/java/com/swmansion/enriched/markdown/input/MarkdownClipboard.kt +42 -0
  28. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/BlockEditCoordinator.kt +196 -0
  29. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/ClipboardCoordinator.kt +73 -0
  30. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/EditContext.kt +19 -0
  31. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/EditPipeline.kt +248 -0
  32. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/EditSession.kt +52 -0
  33. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/InputConnectionWrapper.kt +101 -7
  34. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/LinkCoordinator.kt +69 -0
  35. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/MarkdownTextWatcher.kt +3 -3
  36. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/MentionCoordinator.kt +118 -0
  37. package/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnChangeStateEvent.kt +26 -0
  38. package/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnContextMenuItemPressEvent.kt +26 -0
  39. package/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnInputKeyPressEvent.kt +24 -0
  40. package/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnPasteEvent.kt +24 -0
  41. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/BlockStore.kt +254 -0
  42. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/BlockSyntaxSentinel.kt +69 -0
  43. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/FormattingStore.kt +80 -87
  44. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputFormatter.kt +109 -1
  45. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputParser.kt +180 -27
  46. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/MarkdownSerializer.kt +167 -38
  47. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/RangeEditAdjustment.kt +149 -0
  48. package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputEventEmitter.kt +66 -5
  49. package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputMeasurementStore.kt +7 -2
  50. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/BlockRange.kt +27 -0
  51. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/BlockType.kt +48 -0
  52. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/FormattingRange.kt +9 -4
  53. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/InputFormatterStyle.kt +39 -2
  54. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/MathAtom.kt +20 -0
  55. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/MathChipStyle.kt +1 -1
  56. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/MutableRangeBounds.kt +11 -0
  57. package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputBulletSpan.kt +82 -0
  58. package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputHeadingSpan.kt +58 -0
  59. package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputListItemSpacingSpan.kt +37 -0
  60. package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputOrderedListMarkerSpan.kt +63 -0
  61. package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/BlockHandler.kt +54 -0
  62. package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/HeadingBlockHandler.kt +27 -0
  63. package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/OrderedListBlockHandler.kt +38 -0
  64. package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/UnorderedListBlockHandler.kt +37 -0
  65. package/android/src/main/java/com/swmansion/enriched/markdown/input/toolbar/InputContextMenu.kt +8 -26
  66. package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +1 -0
  67. package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +1 -0
  68. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockStyleContext.kt +9 -2
  69. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +100 -12
  70. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +16 -1
  71. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +4 -0
  72. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListContextManager.kt +2 -1
  73. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +74 -18
  74. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +43 -4
  75. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +9 -1
  76. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SoftBreakRenderer.kt +16 -0
  77. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +30 -27
  78. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BaseListSpan.kt +24 -0
  79. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +174 -24
  80. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +27 -3
  81. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBlockSpan.kt +13 -16
  82. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeSpan.kt +1 -1
  83. package/android/src/main/java/com/swmansion/enriched/markdown/spans/EmphasisSpan.kt +1 -1
  84. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +182 -46
  85. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ListItemSpacingSpan.kt +33 -0
  86. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ListMarkerAnchorSpan.kt +38 -0
  87. package/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt +2 -0
  88. package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrongSpan.kt +1 -1
  89. package/android/src/main/java/com/swmansion/enriched/markdown/spans/TaskListSpan.kt +3 -2
  90. package/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt +3 -2
  91. package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +6 -0
  92. package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +26 -0
  93. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +7 -1
  94. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +4 -1
  95. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +13 -4
  96. package/android/src/main/java/com/swmansion/enriched/markdown/styles/TableStyle.kt +6 -0
  97. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/CodeBlockHighlighter.kt +90 -0
  98. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/CodeBlockNode.kt +57 -0
  99. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/FeatureFlags.kt +1 -0
  100. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownSegment.kt +9 -0
  101. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +33 -7
  102. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/ReadableMapExtensions.kt +3 -0
  103. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/RenderedSegment.kt +10 -0
  104. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/SegmentSignature.kt +1 -0
  105. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/StreamingMarkdownFilter.kt +83 -5
  106. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/serialization/MarkdownASTSerializer.kt +4 -0
  107. package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/MarkdownStyleParser.kt +74 -44
  108. package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/PasteTextUtils.kt +20 -0
  109. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/ImageCache.kt +26 -3
  110. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/ImageDownloader.kt +22 -9
  111. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/LocalImageLoader.kt +161 -0
  112. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/extensions/TextPaintExtensions.kt +23 -0
  113. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/interaction/CheckboxTouchHelper.kt +7 -0
  114. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/interaction/TaskListTapUtils.kt +41 -21
  115. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/span/BlockSpacing.kt +15 -8
  116. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt +58 -27
  117. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +19 -2
  118. package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +388 -0
  119. package/android/src/main/java/com/swmansion/enriched/markdown/views/ContextMenuPopup.kt +9 -6
  120. package/android/src/main/java/com/swmansion/enriched/markdown/views/CopyGlyph.kt +101 -0
  121. package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +32 -5
  122. package/android/src/main/jni/CMakeLists.txt +53 -0
  123. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownContainerShadowNode.cpp +10 -0
  124. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownContainerShadowNode.h +11 -2
  125. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +10 -0
  126. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +12 -2
  127. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +9 -0
  128. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +24 -0
  129. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +16 -0
  130. package/android/src/math/java/com/swmansion/enriched/markdown/input/math/MathChipControllerImpl.kt +21 -4
  131. package/android/src/math/java/com/swmansion/enriched/markdown/renderer/MathInlineRenderer.kt +7 -2
  132. package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathChipSpan.kt +1 -1
  133. package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +2 -0
  134. package/cpp/highlight/CodeBlockHighlighter.cpp +284 -0
  135. package/cpp/highlight/CodeBlockHighlighter.hpp +66 -0
  136. package/cpp/highlight/CodeBlockLanguages.cpp +138 -0
  137. package/cpp/highlight/CodeBlockLanguages.hpp +25 -0
  138. package/cpp/highlight/HighlightGrammars.hpp +28 -0
  139. package/cpp/highlight/code_highlight_podspec.rb +211 -0
  140. package/cpp/highlight/gen-registry.mjs +155 -0
  141. package/cpp/highlight/grammar-versions.json +159 -0
  142. package/cpp/highlight/vendor/generated/generated_queries.h +1665 -0
  143. package/cpp/highlight/vendor/generated/generated_registry.cpp +59 -0
  144. package/cpp/parser/MD4CParser.cpp +173 -61
  145. package/cpp/parser/MD4CParser.hpp +1 -0
  146. package/cpp/parser/MarkdownASTNode.hpp +2 -1
  147. package/cpp/wasm/ASTSerializer.cpp +2 -0
  148. package/cpp/wasm/build.sh +1 -0
  149. package/cpp/wasm/md4c_wasm.cpp +3 -1
  150. package/docs/ACCESSIBILITY.md +14 -2
  151. package/docs/API_REFERENCE.md +231 -8
  152. package/docs/BREAKING_CHANGES.md +129 -0
  153. package/docs/CODE_HIGHLIGHT.md +133 -0
  154. package/docs/COPY_OPTIONS.md +21 -3
  155. package/docs/ELEMENTS_STRUCTURE.md +52 -4
  156. package/docs/IMAGE_CACHING.md +30 -0
  157. package/docs/INPUT.md +146 -5
  158. package/docs/LATEX_MATH.md +87 -45
  159. package/docs/MACOS.md +1 -1
  160. package/docs/MARKDOWN_STREAMING.md +27 -1
  161. package/docs/NATIVE_ASSETS.md +127 -0
  162. package/docs/RTL.md +1 -1
  163. package/docs/STYLES.md +46 -6
  164. package/docs/TESTING.md +112 -0
  165. package/docs/TEXT.md +16 -6
  166. package/docs/WEB.md +3 -1
  167. package/ios/EnrichedMarkdown.h +7 -0
  168. package/ios/EnrichedMarkdown.mm +262 -67
  169. package/ios/EnrichedMarkdownText.h +7 -0
  170. package/ios/EnrichedMarkdownText.mm +97 -68
  171. package/ios/attachments/ENRMImageAttachment.h +9 -0
  172. package/ios/attachments/ENRMImageAttachment.m +143 -40
  173. package/ios/attachments/ENRMMathInlineAttachment.h +2 -0
  174. package/ios/attachments/ENRMMathInlineAttachment.m +48 -81
  175. package/ios/attachments/ENRMMathInlineAttachmentShared.h +2 -0
  176. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +75 -0
  177. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +63 -0
  178. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +40 -0
  179. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +839 -23
  180. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +131 -0
  181. package/ios/input/ENRMAutoLinkDetector.h +2 -1
  182. package/ios/input/ENRMAutoLinkDetector.mm +79 -41
  183. package/ios/input/ENRMBlockEditCoordinator.h +71 -0
  184. package/ios/input/ENRMBlockEditCoordinator.mm +195 -0
  185. package/ios/input/ENRMBlockRange.h +28 -0
  186. package/ios/input/ENRMBlockRange.mm +30 -0
  187. package/ios/input/ENRMBlockStore.h +56 -0
  188. package/ios/input/ENRMBlockStore.mm +275 -0
  189. package/ios/input/ENRMBlockSyntaxSentinel.h +25 -0
  190. package/ios/input/ENRMBlockSyntaxSentinel.mm +80 -0
  191. package/ios/input/ENRMClipboardCoordinator.h +45 -0
  192. package/ios/input/ENRMClipboardCoordinator.mm +120 -0
  193. package/ios/input/ENRMDetectorPipeline.h +2 -1
  194. package/ios/input/ENRMEditPipeline.h +76 -0
  195. package/ios/input/ENRMEditPipeline.mm +228 -0
  196. package/ios/input/ENRMEditSession.h +36 -0
  197. package/ios/input/ENRMEditSession.mm +63 -0
  198. package/ios/input/ENRMFormattingStore.h +17 -0
  199. package/ios/input/ENRMFormattingStore.mm +95 -105
  200. package/ios/input/ENRMInputBlockType.h +91 -0
  201. package/ios/input/ENRMInputBlockType.mm +5 -0
  202. package/ios/input/ENRMInputDecorationDrawer.h +18 -0
  203. package/ios/input/ENRMInputEventEmitter.h +56 -0
  204. package/ios/input/ENRMInputEventEmitter.mm +308 -0
  205. package/ios/input/ENRMInputFormatter.h +29 -69
  206. package/ios/input/ENRMInputFormatter.mm +216 -124
  207. package/ios/input/ENRMInputFormatterStyle.h +99 -0
  208. package/ios/input/ENRMInputFormatterStyle.mm +188 -0
  209. package/ios/input/ENRMInputLayoutManager.h +9 -0
  210. package/ios/input/ENRMInputLayoutManager.mm +30 -0
  211. package/ios/input/ENRMInputListMarkerDrawer.h +32 -0
  212. package/ios/input/ENRMInputListMarkerDrawer.mm +426 -0
  213. package/ios/input/ENRMInputParser.h +10 -6
  214. package/ios/input/ENRMInputParser.mm +527 -171
  215. package/ios/input/ENRMInputRemend.mm +2 -2
  216. package/ios/input/ENRMInputStyleStateBuilder.h +40 -0
  217. package/ios/input/ENRMInputStyleStateBuilder.mm +60 -0
  218. package/ios/input/ENRMInputTextView.mm +103 -8
  219. package/ios/input/ENRMInputTypingAttributesController.h +42 -0
  220. package/ios/input/ENRMInputTypingAttributesController.mm +203 -0
  221. package/ios/input/ENRMLinkCoordinator.h +41 -0
  222. package/ios/input/ENRMLinkCoordinator.mm +89 -0
  223. package/ios/input/ENRMMarkdownSerializer.h +29 -5
  224. package/ios/input/ENRMMarkdownSerializer.mm +170 -48
  225. package/ios/input/ENRMMathAtom.h +26 -0
  226. package/ios/input/ENRMMathAtom.mm +12 -0
  227. package/ios/input/ENRMMentionCoordinator.h +45 -0
  228. package/ios/input/ENRMMentionCoordinator.mm +167 -0
  229. package/ios/input/ENRMRangeEditAdjustment.h +38 -0
  230. package/ios/input/ENRMRangeEditAdjustment.mm +91 -0
  231. package/ios/input/ENRMRangeStoreUtils.h +39 -0
  232. package/ios/input/EnrichedMarkdownTextInput+ContextMenu.mm +6 -6
  233. package/ios/input/EnrichedMarkdownTextInput+Internal.h +11 -3
  234. package/ios/input/EnrichedMarkdownTextInput.h +7 -1
  235. package/ios/input/EnrichedMarkdownTextInput.mm +1104 -1024
  236. package/ios/input/InputStylePropsUtils.h +109 -68
  237. package/ios/input/internals/EnrichedMarkdownTextInputShadowNode.mm +2 -1
  238. package/ios/input/styles/ENRMBlockHandler.h +46 -0
  239. package/ios/input/styles/ENRMHeadingBlockHandler.h +15 -0
  240. package/ios/input/styles/ENRMHeadingBlockHandler.mm +38 -0
  241. package/ios/input/styles/ENRMOrderedListBlockHandler.h +14 -0
  242. package/ios/input/styles/ENRMOrderedListBlockHandler.mm +48 -0
  243. package/ios/input/styles/ENRMUnorderedListBlockHandler.h +15 -0
  244. package/ios/input/styles/ENRMUnorderedListBlockHandler.mm +51 -0
  245. package/ios/internals/ENRMViewFreeMeasurement.h +331 -0
  246. package/ios/internals/EnrichedMarkdownShadowNode.h +1 -3
  247. package/ios/internals/EnrichedMarkdownShadowNode.mm +16 -20
  248. package/ios/internals/EnrichedMarkdownTextShadowNode.h +1 -3
  249. package/ios/internals/EnrichedMarkdownTextShadowNode.mm +15 -20
  250. package/ios/internals/MeasurementCache.h +33 -7
  251. package/ios/internals/ShadowMeasurementUtils.h +63 -58
  252. package/ios/math/ENRMMathChipController.h +74 -0
  253. package/ios/math/ENRMMathChipController.mm +476 -0
  254. package/ios/math/ENRMRaTeXBridge.swift +4 -1
  255. package/ios/parser/ENRMMarkdownParser.h +1 -8
  256. package/ios/parser/ENRMMarkdownParser.mm +2 -11
  257. package/ios/parser/MarkdownASTNode.h +2 -1
  258. package/ios/parser/MarkdownParserBridge.mm +4 -0
  259. package/ios/renderer/AttributedRenderer.m +20 -2
  260. package/ios/renderer/BaseRenderer.h +17 -2
  261. package/ios/renderer/BaseRenderer.m +30 -0
  262. package/ios/renderer/BlockquoteRenderer.h +1 -1
  263. package/ios/renderer/BlockquoteRenderer.m +144 -35
  264. package/ios/renderer/CodeBlockRenderer.h +1 -1
  265. package/ios/renderer/CodeBlockRenderer.m +27 -23
  266. package/ios/renderer/CodeRenderer.h +1 -1
  267. package/ios/renderer/CodeRenderer.m +3 -1
  268. package/ios/renderer/ENRMImageRenderer.h +1 -1
  269. package/ios/renderer/ENRMImageRenderer.m +5 -1
  270. package/ios/renderer/ENRMMathInlineRenderer.h +1 -1
  271. package/ios/renderer/ENRMMathInlineRenderer.m +5 -1
  272. package/ios/renderer/ENRMSpoilerRenderer.h +1 -1
  273. package/ios/renderer/ENRMSpoilerRenderer.m +3 -1
  274. package/ios/renderer/EmphasisRenderer.h +1 -1
  275. package/ios/renderer/EmphasisRenderer.m +3 -1
  276. package/ios/renderer/HeadingRenderer.h +1 -1
  277. package/ios/renderer/HeadingRenderer.m +13 -6
  278. package/ios/renderer/HighlightRenderer.h +1 -1
  279. package/ios/renderer/HighlightRenderer.m +3 -1
  280. package/ios/renderer/LinkRenderer.h +1 -1
  281. package/ios/renderer/LinkRenderer.m +3 -1
  282. package/ios/renderer/ListItemRenderer.h +13 -1
  283. package/ios/renderer/ListItemRenderer.m +102 -44
  284. package/ios/renderer/ListRenderer.h +2 -2
  285. package/ios/renderer/ListRenderer.m +63 -25
  286. package/ios/renderer/ParagraphRenderer.h +1 -1
  287. package/ios/renderer/ParagraphRenderer.m +16 -8
  288. package/ios/renderer/RenderContext.h +10 -0
  289. package/ios/renderer/RenderContext.m +44 -0
  290. package/ios/renderer/RendererFactory.m +14 -0
  291. package/ios/renderer/StrikethroughRenderer.h +1 -1
  292. package/ios/renderer/StrikethroughRenderer.m +3 -1
  293. package/ios/renderer/StrongRenderer.h +1 -1
  294. package/ios/renderer/StrongRenderer.m +3 -1
  295. package/ios/renderer/SubscriptRenderer.h +1 -1
  296. package/ios/renderer/SubscriptRenderer.m +3 -1
  297. package/ios/renderer/SuperscriptRenderer.h +1 -1
  298. package/ios/renderer/SuperscriptRenderer.m +3 -1
  299. package/ios/renderer/TextRenderer.h +2 -2
  300. package/ios/renderer/TextRenderer.m +3 -1
  301. package/ios/renderer/ThematicBreakRenderer.h +1 -1
  302. package/ios/renderer/ThematicBreakRenderer.m +3 -1
  303. package/ios/renderer/UnderlineRenderer.h +1 -1
  304. package/ios/renderer/UnderlineRenderer.m +3 -1
  305. package/ios/styles/StyleConfig.h +22 -0
  306. package/ios/styles/StyleConfig.mm +132 -0
  307. package/ios/utils/BlockquoteBorder.h +6 -0
  308. package/ios/utils/BlockquoteBorder.m +249 -52
  309. package/ios/utils/CodeBlockBackground.m +16 -2
  310. package/ios/utils/ENRMAtomicSize.h +65 -0
  311. package/ios/utils/ENRMCodeBlockContent.h +37 -0
  312. package/ios/utils/ENRMCodeBlockContent.mm +79 -0
  313. package/ios/utils/ENRMCodeBlockHighlighter.h +37 -0
  314. package/ios/utils/ENRMCodeBlockHighlighter.mm +51 -0
  315. package/ios/utils/ENRMFeatureFlags.h +7 -0
  316. package/ios/utils/ENRMImageDownloader.h +11 -1
  317. package/ios/utils/ENRMImageDownloader.m +60 -16
  318. package/ios/utils/ENRMLocalImageLoader.h +31 -0
  319. package/ios/utils/ENRMLocalImageLoader.m +68 -0
  320. package/ios/utils/ENRMMathFallback.h +26 -0
  321. package/ios/utils/ENRMMathFallback.m +18 -0
  322. package/ios/utils/ENRMTextViewSetup.h +35 -17
  323. package/ios/utils/EditMenuUtils.m +22 -1
  324. package/ios/utils/ImageRequestHeaderUtils.h +35 -0
  325. package/ios/utils/LastElementUtils.h +1 -0
  326. package/ios/utils/ListMarkerDrawer.m +29 -42
  327. package/ios/utils/MarkdownASTSerializer.m +4 -0
  328. package/ios/utils/ParagraphStyleUtils.h +5 -0
  329. package/ios/utils/ParagraphStyleUtils.m +77 -17
  330. package/ios/utils/RenderedMarkdownSegment.h +13 -1
  331. package/ios/utils/RenderedMarkdownSegment.m +20 -0
  332. package/ios/utils/SegmentRenderer.m +17 -4
  333. package/ios/utils/StreamingMarkdownFilter.h +11 -1
  334. package/ios/utils/StreamingMarkdownFilter.m +98 -2
  335. package/ios/utils/StylePropsUtils.h +63 -0
  336. package/ios/utils/TaskListTapUtils.m +24 -0
  337. package/ios/views/ENRMCodeBlockContainerView.h +46 -0
  338. package/ios/views/ENRMCodeBlockContainerView.m +566 -0
  339. package/ios/views/ENRMMathContainerView.h +12 -0
  340. package/ios/views/ENRMMathContainerView.m +71 -1
  341. package/ios/views/TableContainerView.h +18 -0
  342. package/ios/views/TableContainerView.m +259 -159
  343. package/jest.d.ts +1 -0
  344. package/jest.js +5 -0
  345. package/lib/module/EnrichedMarkdownNativeComponent.ts +65 -0
  346. package/lib/module/EnrichedMarkdownTextInput.js +86 -5
  347. package/lib/module/EnrichedMarkdownTextInput.js.map +1 -1
  348. package/lib/module/EnrichedMarkdownTextInputNativeComponent.ts +89 -5
  349. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +66 -0
  350. package/lib/module/headingDefaults.js +34 -0
  351. package/lib/module/headingDefaults.js.map +1 -0
  352. package/lib/module/index.js.map +1 -1
  353. package/lib/module/jest/index.js +191 -0
  354. package/lib/module/jest/index.js.map +1 -0
  355. package/lib/module/native/EnrichedMarkdownText.js +30 -4
  356. package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
  357. package/lib/module/normalizeMarkdownStyle.js +81 -19
  358. package/lib/module/normalizeMarkdownStyle.js.map +1 -1
  359. package/lib/module/normalizeMarkdownStyle.web.js +56 -18
  360. package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
  361. package/lib/module/normalizeMarkdownTextInputStyle.js +62 -15
  362. package/lib/module/normalizeMarkdownTextInputStyle.js.map +1 -1
  363. package/lib/module/normalizeMenuItem.js +1 -1
  364. package/lib/module/utils/textInputState.js +26 -0
  365. package/lib/module/utils/textInputState.js.map +1 -0
  366. package/lib/module/web/EnrichedMarkdownText.js +9 -5
  367. package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
  368. package/lib/module/web/parseMarkdown.js +4 -3
  369. package/lib/module/web/parseMarkdown.js.map +1 -1
  370. package/lib/module/web/renderers/InlineRenderers.js +6 -0
  371. package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
  372. package/lib/module/web/renderers/ListRenderers.js +7 -2
  373. package/lib/module/web/renderers/ListRenderers.js.map +1 -1
  374. package/lib/module/web/renderers/index.js +7 -4
  375. package/lib/module/web/renderers/index.js.map +1 -1
  376. package/lib/module/web/styles.js +83 -6
  377. package/lib/module/web/styles.js.map +1 -1
  378. package/lib/module/web/utils.js +1 -0
  379. package/lib/module/web/utils.js.map +1 -1
  380. package/lib/module/web/wasm/md4c.js +0 -0
  381. package/lib/module/web/wasm/md4c.js.map +1 -1
  382. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +62 -0
  383. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
  384. package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts +95 -9
  385. package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts.map +1 -1
  386. package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts +80 -5
  387. package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts.map +1 -1
  388. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +62 -0
  389. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  390. package/lib/typescript/src/headingDefaults.d.ts +9 -0
  391. package/lib/typescript/src/headingDefaults.d.ts.map +1 -0
  392. package/lib/typescript/src/index.d.ts +2 -2
  393. package/lib/typescript/src/index.d.ts.map +1 -1
  394. package/lib/typescript/src/index.web.d.ts +1 -1
  395. package/lib/typescript/src/index.web.d.ts.map +1 -1
  396. package/lib/typescript/src/jest/index.d.ts +6 -0
  397. package/lib/typescript/src/jest/index.d.ts.map +1 -0
  398. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +3 -3
  399. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
  400. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
  401. package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
  402. package/lib/typescript/src/normalizeMarkdownTextInputStyle.d.ts +19 -5
  403. package/lib/typescript/src/normalizeMarkdownTextInputStyle.d.ts.map +1 -1
  404. package/lib/typescript/src/types/MarkdownStyle.d.ts +72 -0
  405. package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
  406. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +27 -0
  407. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
  408. package/lib/typescript/src/types/MarkdownTextProps.d.ts +65 -1
  409. package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
  410. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +19 -0
  411. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
  412. package/lib/typescript/src/types/events.d.ts +19 -0
  413. package/lib/typescript/src/types/events.d.ts.map +1 -1
  414. package/lib/typescript/src/utils/textInputState.d.ts +24 -0
  415. package/lib/typescript/src/utils/textInputState.d.ts.map +1 -0
  416. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
  417. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
  418. package/lib/typescript/src/web/parseMarkdown.d.ts +1 -1
  419. package/lib/typescript/src/web/parseMarkdown.d.ts.map +1 -1
  420. package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
  421. package/lib/typescript/src/web/renderers/ListRenderers.d.ts.map +1 -1
  422. package/lib/typescript/src/web/renderers/index.d.ts +2 -1
  423. package/lib/typescript/src/web/renderers/index.d.ts.map +1 -1
  424. package/lib/typescript/src/web/styles.d.ts +2 -1
  425. package/lib/typescript/src/web/styles.d.ts.map +1 -1
  426. package/lib/typescript/src/web/types.d.ts +6 -2
  427. package/lib/typescript/src/web/types.d.ts.map +1 -1
  428. package/lib/typescript/src/web/utils.d.ts.map +1 -1
  429. package/package.json +36 -9
  430. package/postinstall.mjs +131 -0
  431. package/ratex-version.json +21 -0
  432. package/src/EnrichedMarkdownNativeComponent.ts +65 -0
  433. package/src/EnrichedMarkdownTextInput.tsx +162 -8
  434. package/src/EnrichedMarkdownTextInputNativeComponent.ts +89 -5
  435. package/src/EnrichedMarkdownTextNativeComponent.ts +66 -0
  436. package/src/headingDefaults.ts +21 -0
  437. package/src/index.tsx +9 -0
  438. package/src/index.web.tsx +1 -0
  439. package/src/jest/index.tsx +215 -0
  440. package/src/native/EnrichedMarkdownText.tsx +40 -2
  441. package/src/normalizeMarkdownStyle.ts +102 -16
  442. package/src/normalizeMarkdownStyle.web.ts +63 -15
  443. package/src/normalizeMarkdownTextInputStyle.ts +110 -35
  444. package/src/normalizeMenuItem.ts +1 -1
  445. package/src/types/MarkdownStyle.ts +74 -0
  446. package/src/types/MarkdownStyleInternal.ts +39 -0
  447. package/src/types/MarkdownTextProps.ts +65 -0
  448. package/src/types/MarkdownTextProps.web.ts +19 -0
  449. package/src/types/events.ts +21 -0
  450. package/src/utils/textInputState.ts +35 -0
  451. package/src/web/EnrichedMarkdownText.tsx +16 -2
  452. package/src/web/parseMarkdown.ts +6 -2
  453. package/src/web/renderers/InlineRenderers.tsx +5 -0
  454. package/src/web/renderers/ListRenderers.tsx +14 -2
  455. package/src/web/renderers/index.tsx +6 -2
  456. package/src/web/styles.ts +89 -5
  457. package/src/web/types.ts +6 -1
  458. package/src/web/utils.ts +1 -0
  459. package/src/web/wasm/md4c.js +0 -0
  460. package/vendor-grammars.mjs +511 -0
  461. package/vendor-ratex.mjs +173 -0
  462. package/app.plugin.js +0 -2
  463. package/plugin/build/withAndroidMath.js +0 -19
  464. package/plugin/build/withIosMath.js +0 -28
  465. package/plugin/build/withReactNativeEnrichedMarkdown.js +0 -11
  466. package/plugin/src/withAndroidMath.ts +0 -24
  467. package/plugin/src/withIosMath.ts +0 -35
  468. package/plugin/src/withReactNativeEnrichedMarkdown.ts +0 -17
  469. package/plugin/tsconfig.build.json +0 -16
@@ -1,20 +1,26 @@
1
1
  #import "ENRMInputParser.h"
2
+ #import "ENRMBlockSyntaxSentinel.h"
3
+ #import "ENRMFeatureFlags.h"
2
4
  #import "ENRMFormattingRange.h"
3
5
  #import "ENRMInputRemend.h"
4
6
  #include "md4c.h"
7
+ #include <algorithm>
5
8
  #include <string>
6
9
  #include <vector>
7
10
 
8
11
  @interface ENRMParseResult ()
9
12
  @property (nonatomic, strong, readwrite) NSString *plainText;
10
13
  @property (nonatomic, strong, readwrite) NSArray<ENRMFormattingRange *> *formattingRanges;
14
+ @property (nonatomic, strong, readwrite) NSArray<ENRMBlockRange *> *blockRanges;
11
15
  @property (nonatomic, strong, readwrite) NSArray<ENRMMathAtom *> *mathAtoms;
12
16
  @end
13
17
 
14
18
  @implementation ENRMParseResult
15
19
  @end
16
20
 
17
- @implementation ENRMMathAtom
21
+ @interface ENRMInputParser ()
22
+ /// Shared body of both public entry points. `remend` gates `ENRMInputRemendComplete`.
23
+ - (ENRMParseResult *)parseToPlainTextAndRanges:(NSString *)markdown remend:(BOOL)remend;
18
24
  @end
19
25
 
20
26
  namespace {
@@ -44,6 +50,46 @@ static bool isSupportedSpan(MD_SPANTYPE md4cType, ENRMInputStyleType &outStyleTy
44
50
  return false;
45
51
  }
46
52
 
53
+ // Block-type mapping mirrors kSupportedSpans for the inline pipeline. A block
54
+ // handler extends recognition by adding its md4c block here and, if leveled,
55
+ // reading its detail in resolveBlockLevel below. MD_BLOCK_P maps to the
56
+ // implicit Paragraph default and produces no stored block range.
57
+ struct BlockTypeMapping {
58
+ MD_BLOCKTYPE md4cType;
59
+ ENRMInputBlockType blockType;
60
+ };
61
+
62
+ static const BlockTypeMapping kSupportedBlocks[] = {
63
+ {MD_BLOCK_P, ENRMInputBlockTypeParagraph},
64
+ // MD_BLOCK_H maps to a representative heading type; onEnterBlock resolves the
65
+ // level (via resolveBlockLevel) and rewrites the type to the level-specific
66
+ // ENRMInputBlockTypeHeadingN.
67
+ {MD_BLOCK_H, ENRMInputBlockTypeHeading1},
68
+ };
69
+ static const size_t kSupportedBlockCount = sizeof(kSupportedBlocks) / sizeof(kSupportedBlocks[0]);
70
+
71
+ static bool isSupportedBlock(MD_BLOCKTYPE md4cType, ENRMInputBlockType &outBlockType)
72
+ {
73
+ for (size_t index = 0; index < kSupportedBlockCount; index++) {
74
+ if (kSupportedBlocks[index].md4cType == md4cType) {
75
+ outBlockType = kSupportedBlocks[index].blockType;
76
+ return true;
77
+ }
78
+ }
79
+ return false;
80
+ }
81
+
82
+ // Per-block integer payload (heading level, list depth). md4c exposes this in
83
+ // the block's MD_BLOCK_*_DETAIL struct. Headings read MD_BLOCK_H_DETAIL.level
84
+ // (1-6); other blocks have no level and return 0.
85
+ static NSInteger resolveBlockLevel(MD_BLOCKTYPE blockType, void *detail)
86
+ {
87
+ if (blockType == MD_BLOCK_H && detail) {
88
+ return (NSInteger)(static_cast<MD_BLOCK_H_DETAIL *>(detail)->level);
89
+ }
90
+ return 0;
91
+ }
92
+
47
93
  struct InlineSpanInfo {
48
94
  ENRMInputStyleType type;
49
95
  size_t openingDelimiterByteOffset;
@@ -52,11 +98,38 @@ struct InlineSpanInfo {
52
98
  std::string linkURL;
53
99
  };
54
100
 
101
+ struct BlockInfo {
102
+ ENRMInputBlockType type;
103
+ NSInteger level;
104
+ size_t contentStartByteOffset = kByteOffsetUnset;
105
+ size_t contentEndByteOffset = kByteOffsetUnset;
106
+ };
107
+
108
+ // A `$...$` / `$$...$$` run. Unlike an inline span, math is not styled text: the
109
+ // whole formula collapses to one atomic character in the plain text, so only the
110
+ // raw syntax extent and the LaTeX source matter. The extent is derived from the
111
+ // content bytes plus the delimiter width rather than from `lastTextEnd`, which
112
+ // would be wrong for math opening a span (e.g. "**$x$**", where no text has been
113
+ // emitted since the bold marker).
55
114
  struct MathSpanInfo {
56
115
  bool displayMode;
57
- size_t openingDelimiterByteOffset;
116
+ size_t contentStartByteOffset = kByteOffsetUnset;
58
117
  size_t contentEndByteOffset = kByteOffsetUnset;
59
118
  std::string latex;
119
+
120
+ size_t delimiterLength() const
121
+ {
122
+ return displayMode ? 2 : 1;
123
+ }
124
+ size_t syntaxStartByteOffset() const
125
+ {
126
+ size_t delimiter = delimiterLength();
127
+ return contentStartByteOffset >= delimiter ? contentStartByteOffset - delimiter : 0;
128
+ }
129
+ size_t syntaxEndByteOffset() const
130
+ {
131
+ return contentEndByteOffset + delimiterLength();
132
+ }
60
133
  };
61
134
 
62
135
  struct ParseContext {
@@ -65,9 +138,16 @@ struct ParseContext {
65
138
  size_t originalLength;
66
139
  std::vector<InlineSpanInfo> openStack;
67
140
  std::vector<InlineSpanInfo> resolved;
141
+ std::vector<BlockInfo> openBlockStack;
142
+ std::vector<BlockInfo> resolvedBlocks;
68
143
  std::vector<MathSpanInfo> openMathStack;
69
144
  std::vector<MathSpanInfo> resolvedMath;
145
+ std::vector<size_t> textStartOffsets;
70
146
  size_t lastTextEnd = 0;
147
+ // Open list containers, innermost last (true = ordered). An item's depth is
148
+ // the stack size - 1 and its type the innermost container's; md4c carries no
149
+ // per-item depth, so both derive from the open UL/OL ancestors.
150
+ std::vector<bool> listContainerStack;
71
151
  };
72
152
 
73
153
  static std::vector<NSUInteger> buildByteToUTF16Map(const char *utf8, size_t byteLength)
@@ -108,55 +188,119 @@ static inline NSUInteger mapByteOffset(const std::vector<NSUInteger> &map, size_
108
188
  return map[std::min(offset, maxOffset)];
109
189
  }
110
190
 
111
- static const size_t kClosingDelimiterByteLength[] = {
112
- [ENRMInputStyleTypeStrong] = 2, [ENRMInputStyleTypeEmphasis] = 1, [ENRMInputStyleTypeUnderline] = 1,
113
- [ENRMInputStyleTypeStrikethrough] = 2, [ENRMInputStyleTypeSpoiler] = 2,
114
- };
115
-
116
- static size_t closingDelimiterEndByte(const InlineSpanInfo &span, const char *utf8, size_t bufferLength)
191
+ static size_t closingDelimiterEndByte(const ParseContext &context, const InlineSpanInfo &span)
117
192
  {
118
193
  size_t position = span.contentEndByteOffset;
119
194
 
120
195
  if (span.type == ENRMInputStyleTypeLink) {
121
- while (position < bufferLength && utf8[position] != ')') {
196
+ while (position < context.bufferLength && context.buffer[position] != ')') {
122
197
  position++;
123
198
  }
124
- return (position < bufferLength) ? position + 1 : position;
199
+ return (position < context.bufferLength) ? position + 1 : position;
125
200
  }
126
201
 
127
- return position + kClosingDelimiterByteLength[span.type];
202
+ // Markdown delimiter bytes occupy the gap between content end and the next emitted text run
203
+ // (md4c never emits syntax bytes as text). Anchoring here — rather than adding a
204
+ // fixed per-type length — keeps stacked closers of co-terminating spans (e.g.
205
+ // "***", "**~~x~~**") from overlapping and leaking a trailing marker.
206
+ auto next = std::lower_bound(context.textStartOffsets.begin(), context.textStartOffsets.end(), position);
207
+ return (next != context.textStartOffsets.end()) ? *next : context.bufferLength;
128
208
  }
129
209
 
130
- // TODO: onEnterBlock/onLeaveBlock are no-ops, so lastTextEnd never advances
131
- // past inter-block whitespace. This causes onEnterSpan to set
132
- // openingDelimiterByteOffset to a position that includes \n\n (or other
133
- // block-separator characters) in the syntax range. The newline-stripping
134
- // workaround in parseToPlainTextAndRanges only handles \n/\r — it won't
135
- // catch block-level syntax like list markers, blockquote '>', or heading '#'
136
- // if those elements are added in the future.
137
- //
138
- // A proper fix would advance lastTextEnd here (or retroactively adjust
139
- // openingDelimiterByteOffset for spans opened since the last block
140
- // transition). md4c's enter_block doesn't provide a byte offset, so this
141
- // likely requires tracking a "new block" flag and correcting span offsets
142
- // in onText when the first text inside a new block arrives.
143
- static int onEnterBlock(MD_BLOCKTYPE, void *, void *)
210
+ // Block tracking. md4c's enter_block gives no byte offset, so a block's content
211
+ // range is bounded by the text spans it encloses: onText records the first/last
212
+ // text offset into the currently open block(s). On leave_block the block is
213
+ // resolved with whatever text range it accumulated; empty blocks (no text) are
214
+ // discarded later when building results.
215
+ static int onEnterBlock(MD_BLOCKTYPE blockType, void *detail, void *userdata)
144
216
  {
217
+ auto *context = static_cast<ParseContext *>(userdata);
218
+
219
+ // List nesting is tracked by the container stack, not stored as its own
220
+ // block: the UL/OL container only frames the items that read it.
221
+ if (blockType == MD_BLOCK_UL || blockType == MD_BLOCK_OL) {
222
+ context->listContainerStack.push_back(blockType == MD_BLOCK_OL);
223
+ return 0;
224
+ }
225
+
226
+ // Tag the item itself, not its inner paragraph: md4c emits MD_BLOCK_P inside
227
+ // items only for *loose* lists, so a tight list has no paragraph to tag. The
228
+ // item's range is clipped back to its own first line when building results.
229
+ if (blockType == MD_BLOCK_LI) {
230
+ BlockInfo blockInfo;
231
+ BOOL ordered = !context->listContainerStack.empty() && context->listContainerStack.back();
232
+ blockInfo.type = ordered ? ENRMInputBlockTypeOrderedListItem : ENRMInputBlockTypeUnorderedListItem;
233
+ // Clamp to the shared max depth (matches Android's parser): markdown can
234
+ // nest deeper than the editor represents, and downstream metadata passes
235
+ // size their counters by kENRMMaxListDepth.
236
+ blockInfo.level = context->listContainerStack.empty()
237
+ ? 0
238
+ : MIN((NSInteger)context->listContainerStack.size() - 1, kENRMMaxListDepth);
239
+ context->openBlockStack.push_back(blockInfo);
240
+ return 0;
241
+ }
242
+
243
+ ENRMInputBlockType mappedType;
244
+ if (!isSupportedBlock(blockType, mappedType)) {
245
+ return 0;
246
+ }
247
+
248
+ BlockInfo blockInfo;
249
+ blockInfo.level = resolveBlockLevel(blockType, detail);
250
+ // Headings share one md4c block type but split into six ENRMInputBlockTypes by
251
+ // level; map the resolved level onto the concrete heading type.
252
+ blockInfo.type = (blockType == MD_BLOCK_H) ? ENRMBlockTypeForHeadingLevel(blockInfo.level) : mappedType;
253
+ context->openBlockStack.push_back(blockInfo);
145
254
  return 0;
146
255
  }
147
- static int onLeaveBlock(MD_BLOCKTYPE, void *, void *)
256
+
257
+ static int onLeaveBlock(MD_BLOCKTYPE blockType, void *, void *userdata)
148
258
  {
259
+ auto *context = static_cast<ParseContext *>(userdata);
260
+
261
+ if (blockType == MD_BLOCK_UL || blockType == MD_BLOCK_OL) {
262
+ if (!context->listContainerStack.empty()) {
263
+ context->listContainerStack.pop_back();
264
+ }
265
+ return 0;
266
+ }
267
+
268
+ // List items are tagged in onEnterBlock (not via isSupportedBlock); resolve
269
+ // them here the same way as supported blocks.
270
+ if (blockType == MD_BLOCK_LI) {
271
+ if (!context->openBlockStack.empty()) {
272
+ context->resolvedBlocks.push_back(context->openBlockStack.back());
273
+ context->openBlockStack.pop_back();
274
+ }
275
+ return 0;
276
+ }
277
+
278
+ ENRMInputBlockType mappedType;
279
+ if (!isSupportedBlock(blockType, mappedType)) {
280
+ return 0;
281
+ }
282
+
283
+ if (context->openBlockStack.empty()) {
284
+ return 0;
285
+ }
286
+
287
+ context->resolvedBlocks.push_back(context->openBlockStack.back());
288
+ context->openBlockStack.pop_back();
149
289
  return 0;
150
290
  }
151
291
 
292
+ static inline bool isMathSpan(MD_SPANTYPE spanType)
293
+ {
294
+ return spanType == MD_SPAN_LATEXMATH || spanType == MD_SPAN_LATEXMATH_DISPLAY;
295
+ }
296
+
152
297
  static int onEnterSpan(MD_SPANTYPE spanType, void *detail, void *userdata)
153
298
  {
154
299
  auto *context = static_cast<ParseContext *>(userdata);
155
300
 
156
- if (spanType == MD_SPAN_LATEXMATH || spanType == MD_SPAN_LATEXMATH_DISPLAY) {
301
+ if (isMathSpan(spanType)) {
157
302
  MathSpanInfo mathInfo;
158
303
  mathInfo.displayMode = (spanType == MD_SPAN_LATEXMATH_DISPLAY);
159
- mathInfo.openingDelimiterByteOffset = context->lastTextEnd;
160
304
  context->openMathStack.push_back(mathInfo);
161
305
  return 0;
162
306
  }
@@ -185,7 +329,7 @@ static int onLeaveSpan(MD_SPANTYPE spanType, void *, void *userdata)
185
329
  {
186
330
  auto *context = static_cast<ParseContext *>(userdata);
187
331
 
188
- if (spanType == MD_SPAN_LATEXMATH || spanType == MD_SPAN_LATEXMATH_DISPLAY) {
332
+ if (isMathSpan(spanType)) {
189
333
  if (context->openMathStack.empty()) {
190
334
  return 0;
191
335
  }
@@ -208,25 +352,56 @@ static int onLeaveSpan(MD_SPANTYPE spanType, void *, void *userdata)
208
352
  return 0;
209
353
  }
210
354
 
211
- static int onText(MD_TEXTTYPE textType, const MD_CHAR *text, MD_SIZE size, void *userdata)
355
+ static int onText(MD_TEXTTYPE, const MD_CHAR *text, MD_SIZE size, void *userdata)
212
356
  {
213
357
  if (!text || size == 0) {
214
358
  return 0;
215
359
  }
216
360
  auto *context = static_cast<ParseContext *>(userdata);
217
361
 
218
- // Inside a math span, md4c emits the latex source (typically MD_TEXT_LATEXMATH).
219
- // Accumulate it into the open math info and do not propagate to regular spans.
362
+ // Inside `$...$` md4c emits the LaTeX source as text. It is never document
363
+ // text — the whole formula becomes one placeholder character — so it is
364
+ // collected here and never appended to the plain text. What the enclosing
365
+ // span/block does see is the formula's full *syntax* extent (delimiters
366
+ // included): without that a heading holding only math ("# $x$") would resolve
367
+ // with no content range at all and lose its block prefix.
220
368
  if (!context->openMathStack.empty()) {
221
- (void)textType;
222
- MathSpanInfo &top = context->openMathStack.back();
223
- top.latex.append(text, size);
369
+ MathSpanInfo &math = context->openMathStack.back();
370
+ math.latex.append(text, size);
371
+
224
372
  bool insideBuffer = (text >= context->buffer && text < context->buffer + context->bufferLength);
225
- if (insideBuffer) {
226
- size_t textStart = text - context->buffer;
227
- top.contentEndByteOffset = textStart + size;
228
- context->lastTextEnd = textStart + size;
373
+ if (!insideBuffer) {
374
+ // A synthetic token (soft break inside display math) carries no offset;
375
+ // its characters still belong to the formula, collected above.
376
+ return 0;
229
377
  }
378
+
379
+ size_t textStart = text - context->buffer;
380
+ size_t textEnd = textStart + size;
381
+ bool isFirstChunk = (math.contentStartByteOffset == kByteOffsetUnset);
382
+ if (isFirstChunk) {
383
+ math.contentStartByteOffset = textStart;
384
+ }
385
+ math.contentEndByteOffset = textEnd;
386
+
387
+ size_t syntaxStart = math.syntaxStartByteOffset();
388
+ size_t syntaxEnd = math.syntaxEndByteOffset();
389
+ if (isFirstChunk) {
390
+ context->textStartOffsets.push_back(syntaxStart);
391
+ }
392
+ for (auto &openSpan : context->openStack) {
393
+ if (openSpan.contentStartByteOffset == kByteOffsetUnset) {
394
+ openSpan.contentStartByteOffset = syntaxStart;
395
+ }
396
+ openSpan.contentEndByteOffset = syntaxEnd;
397
+ }
398
+ for (auto &openBlock : context->openBlockStack) {
399
+ if (openBlock.contentStartByteOffset == kByteOffsetUnset) {
400
+ openBlock.contentStartByteOffset = syntaxStart;
401
+ }
402
+ openBlock.contentEndByteOffset = syntaxEnd;
403
+ }
404
+ context->lastTextEnd = syntaxEnd;
230
405
  return 0;
231
406
  }
232
407
 
@@ -234,10 +409,9 @@ static int onText(MD_TEXTTYPE textType, const MD_CHAR *text, MD_SIZE size, void
234
409
  // (e.g. MD_TEXT_SOFTBR, MD_TEXT_BR use a string literal "\n").
235
410
  // Pointer arithmetic against context->buffer would underflow, corrupting offsets.
236
411
  //
237
- // TODO: Skipping out-of-buffer tokens means lastTextEnd doesn't advance past
238
- // newlines. Fine for inline spans, but if block-level input formatting is
239
- // added later, openingDelimiterByteOffset will include newline chars in the
240
- // syntax range. See the related TODO above onEnterBlock.
412
+ // Skipping out-of-buffer tokens means lastTextEnd doesn't advance past
413
+ // synthetic newlines. Inline-span offsets stay correct because spans never
414
+ // straddle a synthetic break; block offsets are derived from real text below.
241
415
  bool insideBuffer = (text >= context->buffer && text < context->buffer + context->bufferLength);
242
416
  if (!insideBuffer) {
243
417
  return 0;
@@ -245,6 +419,7 @@ static int onText(MD_TEXTTYPE textType, const MD_CHAR *text, MD_SIZE size, void
245
419
 
246
420
  size_t textStart = text - context->buffer;
247
421
  size_t textEnd = textStart + size;
422
+ context->textStartOffsets.push_back(textStart);
248
423
 
249
424
  for (auto &openSpan : context->openStack) {
250
425
  if (openSpan.contentStartByteOffset == kByteOffsetUnset) {
@@ -252,13 +427,29 @@ static int onText(MD_TEXTTYPE textType, const MD_CHAR *text, MD_SIZE size, void
252
427
  }
253
428
  openSpan.contentEndByteOffset = textEnd;
254
429
  }
430
+ for (auto &openBlock : context->openBlockStack) {
431
+ if (openBlock.contentStartByteOffset == kByteOffsetUnset) {
432
+ openBlock.contentStartByteOffset = textStart;
433
+ }
434
+ openBlock.contentEndByteOffset = textEnd;
435
+ }
255
436
  context->lastTextEnd = textEnd;
256
437
  return 0;
257
438
  }
258
439
 
259
- static bool runMd4cParse(NSString *markdown, ParseContext &context)
440
+ // The one place math recognition is switched off in a math-disabled build: with
441
+ // the flag clear md4c never opens a LATEXMATH span, so no atom is produced and
442
+ // "$x$" stays literal text — mirroring Android, where `Md4cFlags.latexMath`
443
+ // defaults from `FeatureFlags.IS_MATH_ENABLED`.
444
+ #if ENRICHED_MARKDOWN_MATH
445
+ static const unsigned kMathSpanFlags = MD_FLAG_LATEXMATHSPANS;
446
+ #else
447
+ static const unsigned kMathSpanFlags = 0;
448
+ #endif
449
+
450
+ static bool runMd4cParse(NSString *markdown, ParseContext &context, bool remend)
260
451
  {
261
- NSString *completed = ENRMInputRemendComplete(markdown);
452
+ NSString *completed = remend ? ENRMInputRemendComplete(markdown) : markdown;
262
453
  const char *completedUTF8 = [completed UTF8String];
263
454
  size_t completedLength = strlen(completedUTF8);
264
455
  size_t originalLength = [markdown lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
@@ -269,7 +460,7 @@ static bool runMd4cParse(NSString *markdown, ParseContext &context)
269
460
 
270
461
  MD_PARSER parser = {
271
462
  .abi_version = 0,
272
- .flags = MD_FLAG_NOHTML | MD_FLAG_UNDERLINE | MD_FLAG_STRIKETHROUGH | MD_FLAG_SPOILERS | MD_FLAG_LATEXMATHSPANS,
463
+ .flags = MD_FLAG_NOHTML | MD_FLAG_UNDERLINE | MD_FLAG_STRIKETHROUGH | MD_FLAG_SPOILERS | kMathSpanFlags,
273
464
  .enter_block = onEnterBlock,
274
465
  .leave_block = onLeaveBlock,
275
466
  .enter_span = onEnterSpan,
@@ -282,25 +473,12 @@ static bool runMd4cParse(NSString *markdown, ParseContext &context)
282
473
  return md_parse(completedUTF8, (MD_SIZE)completedLength, &parser, &context) == 0;
283
474
  }
284
475
 
285
- struct ParsedSpans {
286
- std::vector<MathSpanInfo> mathSpans;
287
- };
288
-
289
- static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, ParsedSpans &out)
476
+ // Builds inline styled ranges (raw-markdown UTF-16 coords) from a completed
477
+ // parse. Split out so parseToPlainTextAndRanges: can derive inline and block
478
+ // ranges from ONE md4c run instead of parsing twice.
479
+ static NSArray<ENRMInputStyledRange *> *styledRangesFromContext(const ParseContext &context,
480
+ const std::vector<NSUInteger> &byteMap)
290
481
  {
291
- if (markdown.length == 0) {
292
- return @[];
293
- }
294
-
295
- ParseContext context;
296
- if (!runMd4cParse(markdown, context)) {
297
- return @[];
298
- }
299
-
300
- out.mathSpans = context.resolvedMath;
301
-
302
- auto byteMap = buildByteToUTF16Map(context.buffer, context.bufferLength);
303
-
304
482
  NSMutableArray<ENRMInputStyledRange *> *results = [NSMutableArray arrayWithCapacity:context.resolved.size()];
305
483
 
306
484
  for (const auto &spanInfo : context.resolved) {
@@ -323,8 +501,7 @@ static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, Par
323
501
  NSUInteger openStart = mapByteOffset(byteMap, spanInfo.openingDelimiterByteOffset, context.bufferLength);
324
502
  NSRange openingRange = NSMakeRange(openStart, contentStart - openStart);
325
503
 
326
- size_t closingEndByte =
327
- std::min(closingDelimiterEndByte(spanInfo, context.buffer, context.bufferLength), context.bufferLength);
504
+ size_t closingEndByte = std::min(closingDelimiterEndByte(context, spanInfo), context.bufferLength);
328
505
  NSUInteger closingStart = mapByteOffset(byteMap, spanInfo.contentEndByteOffset, context.bufferLength);
329
506
  NSUInteger closingEnd = mapByteOffset(byteMap, closingEndByte, context.bufferLength);
330
507
  NSRange closingRange = NSMakeRange(closingStart, closingEnd - closingStart);
@@ -348,75 +525,221 @@ static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, Par
348
525
  return results;
349
526
  }
350
527
 
528
+ // Builds block-level ranges (raw-markdown UTF-16 coords) from the same
529
+ // completed parse, mirroring styledRangesFromContext for inline spans.
530
+ // Paragraph blocks (the implicit default) are omitted — only blocks a handler
531
+ // claims are returned.
532
+ static NSArray<ENRMBlockRange *> *blockRangesFromContext(const ParseContext &context,
533
+ const std::vector<NSUInteger> &byteMap, NSString *markdown)
534
+ {
535
+ NSMutableArray<ENRMBlockRange *> *results = [NSMutableArray arrayWithCapacity:context.resolvedBlocks.size()];
536
+
537
+ for (const auto &blockInfo : context.resolvedBlocks) {
538
+ if (blockInfo.type == ENRMInputBlockTypeParagraph) {
539
+ continue;
540
+ }
541
+ if (blockInfo.contentStartByteOffset == kByteOffsetUnset || blockInfo.contentEndByteOffset == kByteOffsetUnset ||
542
+ blockInfo.contentStartByteOffset > context.originalLength) {
543
+ continue;
544
+ }
545
+
546
+ NSUInteger contentStart = mapByteOffset(byteMap, blockInfo.contentStartByteOffset, context.bufferLength);
547
+ NSUInteger contentEnd = mapByteOffset(byteMap, blockInfo.contentEndByteOffset, context.bufferLength);
548
+ if (contentEnd <= contentStart) {
549
+ continue;
550
+ }
551
+
552
+ // A list item accumulates all text in its subtree, so a parent item's range
553
+ // runs through its nested sublist. Clip each item to its own first line so
554
+ // nested items keep their own (deeper) depth rather than being overwritten
555
+ // by the parent's range. Input list items are single-line.
556
+ if (ENRMBlockTypeIsListItem(blockInfo.type) && contentEnd <= markdown.length) {
557
+ NSRange newline = [markdown rangeOfString:@"\n"
558
+ options:0
559
+ range:NSMakeRange(contentStart, contentEnd - contentStart)];
560
+ if (newline.location != NSNotFound) {
561
+ contentEnd = newline.location;
562
+ }
563
+ if (contentEnd <= contentStart) {
564
+ continue;
565
+ }
566
+ }
567
+
568
+ [results addObject:[ENRMBlockRange rangeWithType:blockInfo.type
569
+ range:NSMakeRange(contentStart, contentEnd - contentStart)
570
+ level:blockInfo.level]];
571
+ }
572
+
573
+ return results;
574
+ }
575
+
576
+ // A resolved math chip in raw-markdown UTF-16 coords: the full `$...$` extent to
577
+ // replace, plus what to replace it with.
578
+ struct MathChip {
579
+ NSRange rawRange;
580
+ NSString *latex;
581
+ BOOL displayMode;
582
+ };
583
+
584
+ // Builds math chips from the same completed parse, mirroring the two functions
585
+ // above. A formula whose closing delimiter exists only because ENRMInputRemend
586
+ // completed the buffer is dropped: while "$x" is still being typed it must stay
587
+ // literal text, the same way an unclosed "**" does (see `isComplete`).
588
+ static std::vector<MathChip> mathChipsFromContext(const ParseContext &context, const std::vector<NSUInteger> &byteMap,
589
+ NSUInteger rawLength)
590
+ {
591
+ std::vector<MathChip> chips;
592
+ chips.reserve(context.resolvedMath.size());
593
+
594
+ for (const auto &math : context.resolvedMath) {
595
+ // No content bytes at all ("$$"): not a formula, leave it as text.
596
+ if (math.contentStartByteOffset == kByteOffsetUnset || math.contentEndByteOffset == kByteOffsetUnset) {
597
+ continue;
598
+ }
599
+ size_t syntaxEnd = math.syntaxEndByteOffset();
600
+ if (syntaxEnd > context.originalLength) {
601
+ continue;
602
+ }
603
+
604
+ NSUInteger start = mapByteOffset(byteMap, math.syntaxStartByteOffset(), context.bufferLength);
605
+ NSUInteger end = mapByteOffset(byteMap, syntaxEnd, context.bufferLength);
606
+ if (end <= start || end > rawLength) {
607
+ continue;
608
+ }
609
+
610
+ MathChip chip;
611
+ chip.rawRange = NSMakeRange(start, end - start);
612
+ chip.latex = [[NSString alloc] initWithBytes:math.latex.data()
613
+ length:math.latex.size()
614
+ encoding:NSUTF8StringEncoding]
615
+ ?: @"";
616
+ chip.displayMode = math.displayMode ? YES : NO;
617
+ chips.push_back(chip);
618
+ }
619
+
620
+ std::sort(chips.begin(), chips.end(), [](const MathChip &first, const MathChip &second) {
621
+ return first.rawRange.location < second.rawRange.location;
622
+ });
623
+ return chips;
624
+ }
625
+
351
626
  } // namespace
352
627
 
353
628
  @implementation ENRMInputParser
354
629
 
355
630
  - (NSArray<ENRMInputStyledRange *> *)parse:(NSString *)markdown
356
631
  {
357
- ParsedSpans spans;
358
- return runParseInternal(markdown, spans);
632
+ if (markdown.length == 0) {
633
+ return @[];
634
+ }
635
+
636
+ ParseContext context;
637
+ if (!runMd4cParse(markdown, context, /* remend */ true)) {
638
+ return @[];
639
+ }
640
+
641
+ auto byteMap = buildByteToUTF16Map(context.buffer, context.bufferLength);
642
+ return styledRangesFromContext(context, byteMap);
359
643
  }
360
644
 
361
645
  - (ENRMParseResult *)parseToPlainTextAndRanges:(NSString *)markdown
362
646
  {
363
- ENRMParseResult *parseResult = [[ENRMParseResult alloc] init];
647
+ return [self parseToPlainTextAndRanges:markdown remend:YES];
648
+ }
364
649
 
365
- if (markdown.length == 0) {
366
- parseResult.plainText = @"";
367
- parseResult.formattingRanges = @[];
368
- parseResult.mathAtoms = @[];
369
- return parseResult;
650
+ - (ENRMParseResult *)parseToPlainTextAndRanges:(NSString *)markdown blockSyntax:(BOOL)blockSyntax
651
+ {
652
+ if (blockSyntax) {
653
+ return [self parseToPlainTextAndRanges:markdown];
370
654
  }
371
655
 
372
- ParsedSpans parsedSpans;
373
- NSArray<ENRMInputStyledRange *> *styledRanges = runParseInternal(markdown, parsedSpans);
656
+ ENRMParseResult *result = [[ENRMParseResult alloc] init];
657
+ // Block ranges stay empty on every path below: with block syntax off there are
658
+ // no blocks. Under the sentinel md4c sees one paragraph, which yields no range, so
659
+ // this is a guard rather than a fix — a range that did leak would reach the block
660
+ // store and the serializer would prefix it, breaking the byte-for-byte round trip.
661
+ result.blockRanges = @[];
662
+
663
+ NSString *canonical = ENRMBlockSyntaxCanonicalize(markdown);
664
+ if (canonical.length == 0) {
665
+ result.plainText = @"";
666
+ result.formattingRanges = @[];
667
+ result.mathAtoms = @[];
668
+ return result;
669
+ }
374
670
 
375
- // Compute byte map + raw byte ranges for math spans so we can strip the
376
- // entire `$...$` (or `$$...$$`) syntax and inject a single `` at each.
377
- size_t rawByteLength = [markdown lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
378
- const char *rawUTF8 = [markdown UTF8String];
379
- std::vector<NSUInteger> rawByteMap = buildByteToUTF16Map(rawUTF8, rawByteLength);
671
+ // Remending is skipped too: its speculative closing delimiters are the other
672
+ // source of characters the serializer cannot replay.
673
+ ENRMParseResult *encoded = [self parseToPlainTextAndRanges:ENRMBlockSyntaxEncode(canonical) remend:NO];
380
674
 
381
- struct MathChip {
382
- NSRange rawSyntaxRange; // UTF-16 range in the original markdown to strip
383
- NSString *latex;
384
- BOOL displayMode;
385
- };
386
- std::vector<MathChip> mathChips;
387
- mathChips.reserve(parsedSpans.mathSpans.size());
388
- for (const auto &mathInfo : parsedSpans.mathSpans) {
389
- if (mathInfo.contentEndByteOffset == kByteOffsetUnset)
390
- continue;
391
- size_t closingByteEnd = mathInfo.contentEndByteOffset + (mathInfo.displayMode ? 2 : 1);
392
- if (closingByteEnd > rawByteLength)
393
- closingByteEnd = rawByteLength;
394
- if (mathInfo.openingDelimiterByteOffset > rawByteLength)
395
- continue;
396
- if (closingByteEnd > rawByteLength)
397
- continue;
675
+ NSString *decoded = ENRMBlockSyntaxDecode(encoded.plainText);
676
+ if (decoded == nil) {
677
+ result.plainText = canonical;
678
+ result.formattingRanges = @[];
679
+ result.mathAtoms = @[];
680
+ return result;
681
+ }
682
+
683
+ // Only the leading sentinel was removed, so every offset shifts by exactly one.
684
+ NSUInteger decodedLength = decoded.length;
398
685
 
399
- NSUInteger rawStart = rawByteMap[mathInfo.openingDelimiterByteOffset];
400
- NSUInteger rawEnd = rawByteMap[closingByteEnd];
401
- if (rawEnd <= rawStart)
686
+ NSMutableArray<ENRMFormattingRange *> *shiftedRanges =
687
+ [NSMutableArray arrayWithCapacity:encoded.formattingRanges.count];
688
+ for (ENRMFormattingRange *range in encoded.formattingRanges) {
689
+ NSRange r = range.range;
690
+ if (r.location == 0 || r.length == 0 || NSMaxRange(r) - 1 > decodedLength) {
402
691
  continue;
692
+ }
693
+ [shiftedRanges addObject:[ENRMFormattingRange rangeWithType:range.type
694
+ range:NSMakeRange(r.location - 1, r.length)
695
+ url:range.url]];
696
+ }
403
697
 
404
- MathChip chip;
405
- chip.rawSyntaxRange = NSMakeRange(rawStart, rawEnd - rawStart);
406
- chip.latex = [[NSString alloc] initWithBytes:mathInfo.latex.data()
407
- length:mathInfo.latex.size()
408
- encoding:NSUTF8StringEncoding]
409
- ?: @"";
410
- chip.displayMode = mathInfo.displayMode ? YES : NO;
411
- mathChips.push_back(chip);
698
+ NSMutableArray<ENRMMathAtom *> *shiftedAtoms = [NSMutableArray arrayWithCapacity:encoded.mathAtoms.count];
699
+ for (ENRMMathAtom *atom in encoded.mathAtoms) {
700
+ if (atom.position == 0 || atom.position - 1 >= decodedLength) {
701
+ continue;
702
+ }
703
+ ENRMMathAtom *shifted = [[ENRMMathAtom alloc] init];
704
+ shifted.position = atom.position - 1;
705
+ shifted.latex = atom.latex;
706
+ shifted.displayMode = atom.displayMode;
707
+ [shiftedAtoms addObject:shifted];
412
708
  }
413
709
 
414
- // Sort math chips by raw position so we process left-to-right.
415
- std::sort(mathChips.begin(), mathChips.end(),
416
- [](const MathChip &a, const MathChip &b) { return a.rawSyntaxRange.location < b.rawSyntaxRange.location; });
710
+ result.plainText = decoded;
711
+ result.formattingRanges = shiftedRanges;
712
+ result.mathAtoms = shiftedAtoms;
713
+ return result;
714
+ }
715
+
716
+ - (ENRMParseResult *)parseToPlainTextAndRanges:(NSString *)markdown remend:(BOOL)remend
717
+ {
718
+ ENRMParseResult *parseResult = [[ENRMParseResult alloc] init];
719
+
720
+ if (markdown.length == 0) {
721
+ parseResult.plainText = @"";
722
+ parseResult.formattingRanges = @[];
723
+ parseResult.blockRanges = @[];
724
+ parseResult.mathAtoms = @[];
725
+ return parseResult;
726
+ }
417
727
 
418
728
  NSUInteger rawLength = markdown.length;
419
729
 
730
+ // One md4c run feeds all three pipelines: inline styled ranges, block ranges
731
+ // and math chips are derived from the same ParseContext.
732
+ ParseContext context;
733
+ NSArray<ENRMInputStyledRange *> *styledRanges = @[];
734
+ NSArray<ENRMBlockRange *> *rawBlockRanges = @[];
735
+ std::vector<MathChip> mathChips;
736
+ if (runMd4cParse(markdown, context, remend)) {
737
+ auto byteMap = buildByteToUTF16Map(context.buffer, context.bufferLength);
738
+ styledRanges = styledRangesFromContext(context, byteMap);
739
+ rawBlockRanges = blockRangesFromContext(context, byteMap, markdown);
740
+ mathChips = mathChipsFromContext(context, byteMap, rawLength);
741
+ }
742
+
420
743
  NSMutableIndexSet *syntaxIndexes = [NSMutableIndexSet indexSet];
421
744
  for (ENRMInputStyledRange *styledRange in styledRanges) {
422
745
  if (!styledRange.isComplete)
@@ -432,19 +755,28 @@ static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, Par
432
755
  }
433
756
  }
434
757
 
435
- // Add math full ranges (delimiters + latex) to the strip set.
436
- for (const auto &chip : mathChips) {
437
- NSRange r = chip.rawSyntaxRange;
438
- if (NSMaxRange(r) > rawLength) {
439
- r.length = (r.location < rawLength) ? rawLength - r.location : 0;
758
+ // Strip block markers (e.g. "# ") from plain text — same as inline delimiters.
759
+ // Without this the marker survives and the serializer doubles it ("# # ").
760
+ for (ENRMBlockRange *rawBlock in rawBlockRanges) {
761
+ NSUInteger contentStart = rawBlock.range.location;
762
+ if (contentStart == 0 || contentStart > rawLength) {
763
+ continue;
764
+ }
765
+ NSUInteger lineStart = contentStart;
766
+ while (lineStart > 0) {
767
+ unichar previous = [markdown characterAtIndex:lineStart - 1];
768
+ if (previous == '\n' || previous == '\r') {
769
+ break;
770
+ }
771
+ lineStart--;
440
772
  }
441
- if (r.length > 0) {
442
- [syntaxIndexes addIndexesInRange:r];
773
+ if (contentStart > lineStart) {
774
+ [syntaxIndexes addIndexesInRange:NSMakeRange(lineStart, contentStart - lineStart)];
443
775
  }
444
776
  }
445
777
 
446
778
  // Strip \n/\r from syntax ranges — newlines are structural content, not
447
- // markdown syntax (included due to no-op onEnterBlock/onLeaveBlock).
779
+ // markdown syntax, and must survive into the plain text.
448
780
  NSMutableIndexSet *newlineIndexes = [NSMutableIndexSet indexSet];
449
781
  [syntaxIndexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) {
450
782
  if (index < rawLength) {
@@ -456,32 +788,73 @@ static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, Par
456
788
  }];
457
789
  [syntaxIndexes removeIndexes:newlineIndexes];
458
790
 
791
+ // Build the plain text and the raw→plain map in one pass. Three kinds of raw
792
+ // index: a math chip's extent collapses to one placeholder character, a syntax
793
+ // index is dropped, everything else is copied verbatim.
794
+ //
795
+ // The math substitution happens *here*, not as an insertion afterwards, and
796
+ // that is the whole point: `rawToPlainMap` stays the single authority, so the
797
+ // inline ranges AND the block ranges below both map through it unchanged.
798
+ // Shifting only one of the two is the silent failure this ordering prevents —
799
+ // one placeholder is n raw characters, so every block boundary after a chip
800
+ // would land a line off and "# "/"- "/"1. " would prefix the wrong line.
459
801
  NSMutableString *plainText = [NSMutableString stringWithCapacity:rawLength];
460
- __block std::vector<NSUInteger> rawToPlainMap(rawLength + 1, 0);
802
+ std::vector<NSUInteger> rawToPlainMap(rawLength + 1, 0);
803
+ NSMutableArray<ENRMMathAtom *> *mathAtoms = [NSMutableArray arrayWithCapacity:mathChips.size()];
461
804
 
462
- __block NSUInteger plainPosition = 0;
463
- __block NSUInteger previousEnd = 0;
805
+ NSUInteger plainPosition = 0;
806
+ NSUInteger verbatimStart = 0;
807
+ size_t nextChip = 0;
808
+
809
+ // Copies the pending verbatim run [from, to) into the plain text.
810
+ auto flushVerbatim = [&](NSUInteger from, NSUInteger to) {
811
+ if (to <= from) {
812
+ return;
813
+ }
814
+ [plainText appendString:[markdown substringWithRange:NSMakeRange(from, to - from)]];
815
+ for (NSUInteger rawIndex = from; rawIndex < to; rawIndex++) {
816
+ rawToPlainMap[rawIndex] = plainPosition++;
817
+ }
818
+ };
464
819
 
465
- [syntaxIndexes enumerateRangesUsingBlock:^(NSRange syntaxRange, BOOL *stop) {
466
- if (syntaxRange.location > previousEnd) {
467
- NSRange visibleRange = NSMakeRange(previousEnd, syntaxRange.location - previousEnd);
468
- [plainText appendString:[markdown substringWithRange:visibleRange]];
469
- for (NSUInteger rawIndex = previousEnd; rawIndex < syntaxRange.location; rawIndex++) {
470
- rawToPlainMap[rawIndex] = plainPosition++;
820
+ NSUInteger rawIndex = 0;
821
+ while (rawIndex < rawLength) {
822
+ if (nextChip < mathChips.size() && mathChips[nextChip].rawRange.location == rawIndex) {
823
+ const MathChip &chip = mathChips[nextChip];
824
+ NSUInteger chipEnd = MIN(NSMaxRange(chip.rawRange), rawLength);
825
+ flushVerbatim(verbatimStart, rawIndex);
826
+
827
+ ENRMMathAtom *atom = [[ENRMMathAtom alloc] init];
828
+ atom.position = plainPosition;
829
+ atom.latex = chip.latex;
830
+ atom.displayMode = chip.displayMode;
831
+ [mathAtoms addObject:atom];
832
+
833
+ [plainText appendString:ENRMObjectReplacementString()];
834
+ // Every character of the formula — delimiters included — collapses onto the
835
+ // placeholder, so a range that starts inside it starts at the chip.
836
+ for (NSUInteger index = rawIndex; index < chipEnd; index++) {
837
+ rawToPlainMap[index] = plainPosition;
471
838
  }
839
+ plainPosition++;
840
+
841
+ rawIndex = chipEnd;
842
+ verbatimStart = rawIndex;
843
+ nextChip++;
844
+ continue;
472
845
  }
473
- for (NSUInteger rawIndex = syntaxRange.location; rawIndex < NSMaxRange(syntaxRange); rawIndex++) {
846
+
847
+ if ([syntaxIndexes containsIndex:rawIndex]) {
848
+ flushVerbatim(verbatimStart, rawIndex);
474
849
  rawToPlainMap[rawIndex] = plainPosition;
850
+ rawIndex++;
851
+ verbatimStart = rawIndex;
852
+ continue;
475
853
  }
476
- previousEnd = NSMaxRange(syntaxRange);
477
- }];
478
854
 
479
- if (previousEnd < rawLength) {
480
- [plainText appendString:[markdown substringFromIndex:previousEnd]];
481
- for (NSUInteger rawIndex = previousEnd; rawIndex < rawLength; rawIndex++) {
482
- rawToPlainMap[rawIndex] = plainPosition++;
483
- }
855
+ rawIndex++;
484
856
  }
857
+ flushVerbatim(verbatimStart, rawLength);
485
858
  rawToPlainMap[rawLength] = plainPosition;
486
859
 
487
860
  NSMutableArray<ENRMFormattingRange *> *formattingRanges = [NSMutableArray arrayWithCapacity:styledRanges.count];
@@ -509,46 +882,29 @@ static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, Par
509
882
  [formattingRanges addObject:formattingRange];
510
883
  }
511
884
 
512
- // Inject `` (U+FFFC, NSAttachmentCharacter) at each math chip's plain
513
- // position and emit ENRMMathAtom records pointing at the inserted character.
514
- NSMutableArray<ENRMMathAtom *> *mathAtoms = [NSMutableArray arrayWithCapacity:mathChips.size()];
515
- static unichar attachmentChar = (unichar)0xFFFC;
516
- NSString *attachmentString = [NSString stringWithCharacters:&attachmentChar length:1];
517
- NSUInteger insertionsSoFar = 0;
518
- for (const auto &chip : mathChips) {
519
- NSUInteger rawStart = chip.rawSyntaxRange.location;
520
- if (rawStart > rawLength)
885
+ // Map block content ranges (raw-markdown coords) onto plain-text coords. The
886
+ // block's marker syntax was stripped above, so the content range maps cleanly
887
+ // onto the post-strip text and the block no longer covers the marker.
888
+ NSMutableArray<ENRMBlockRange *> *blockRanges = [NSMutableArray arrayWithCapacity:rawBlockRanges.count];
889
+ for (ENRMBlockRange *rawBlock in rawBlockRanges) {
890
+ NSUInteger contentStart = rawBlock.range.location;
891
+ NSUInteger contentEnd = NSMaxRange(rawBlock.range);
892
+ if (contentStart > rawLength || contentEnd > rawLength)
893
+ continue;
894
+
895
+ NSUInteger plainStart = rawToPlainMap[contentStart];
896
+ NSUInteger plainEnd = rawToPlainMap[contentEnd];
897
+ if (plainEnd <= plainStart)
521
898
  continue;
522
- NSUInteger basePlain = rawToPlainMap[rawStart];
523
- NSUInteger insertAt = basePlain + insertionsSoFar;
524
- if (insertAt > plainText.length)
525
- insertAt = plainText.length;
526
- [plainText insertString:attachmentString atIndex:insertAt];
527
-
528
- ENRMMathAtom *atom = [[ENRMMathAtom alloc] init];
529
- atom.position = insertAt;
530
- atom.latex = chip.latex ?: @"";
531
- atom.displayMode = chip.displayMode;
532
- [mathAtoms addObject:atom];
533
-
534
- // Shift existing formatting ranges that start at or after this insertion.
535
- for (NSUInteger idx = 0; idx < formattingRanges.count; idx++) {
536
- ENRMFormattingRange *fr = formattingRanges[idx];
537
- NSRange r = fr.range;
538
- if (r.location >= insertAt) {
539
- r.location += 1;
540
- formattingRanges[idx] = [ENRMFormattingRange rangeWithType:fr.type range:r url:fr.url];
541
- } else if (NSMaxRange(r) > insertAt) {
542
- r.length += 1;
543
- formattingRanges[idx] = [ENRMFormattingRange rangeWithType:fr.type range:r url:fr.url];
544
- }
545
- }
546
899
 
547
- insertionsSoFar++;
900
+ [blockRanges addObject:[ENRMBlockRange rangeWithType:rawBlock.type
901
+ range:NSMakeRange(plainStart, plainEnd - plainStart)
902
+ level:rawBlock.level]];
548
903
  }
549
904
 
550
905
  parseResult.plainText = plainText;
551
906
  parseResult.formattingRanges = formattingRanges;
907
+ parseResult.blockRanges = blockRanges;
552
908
  parseResult.mathAtoms = mathAtoms;
553
909
  return parseResult;
554
910
  }