react-native-enriched-markdown-scaffold 0.7.4-scaffold.9 → 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 (466) hide show
  1. package/README.md +54 -8
  2. package/ReactNativeEnrichedMarkdown.podspec +82 -73
  3. package/android/build.gradle +186 -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 +24 -0
  8. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerInterface.java +8 -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 +35 -0
  14. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +838 -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 +54 -4
  26. package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +901 -462
  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/FormattingStore.kt +80 -87
  43. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputFormatter.kt +109 -1
  44. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputParser.kt +116 -22
  45. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/MarkdownSerializer.kt +167 -38
  46. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/RangeEditAdjustment.kt +149 -0
  47. package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputEventEmitter.kt +66 -5
  48. package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputMeasurementStore.kt +4 -2
  49. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/BlockRange.kt +27 -0
  50. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/BlockType.kt +48 -0
  51. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/FormattingRange.kt +9 -4
  52. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/InputFormatterStyle.kt +39 -2
  53. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/MathAtom.kt +20 -0
  54. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/MathChipStyle.kt +1 -1
  55. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/MutableRangeBounds.kt +11 -0
  56. package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputBulletSpan.kt +82 -0
  57. package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputHeadingSpan.kt +58 -0
  58. package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputListItemSpacingSpan.kt +37 -0
  59. package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputOrderedListMarkerSpan.kt +63 -0
  60. package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/BlockHandler.kt +54 -0
  61. package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/HeadingBlockHandler.kt +27 -0
  62. package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/OrderedListBlockHandler.kt +38 -0
  63. package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/UnorderedListBlockHandler.kt +37 -0
  64. package/android/src/main/java/com/swmansion/enriched/markdown/input/toolbar/InputContextMenu.kt +8 -26
  65. package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +1 -0
  66. package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +1 -0
  67. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockStyleContext.kt +9 -2
  68. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +100 -12
  69. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +16 -1
  70. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +4 -0
  71. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListContextManager.kt +2 -1
  72. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +74 -18
  73. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +43 -4
  74. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +9 -1
  75. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SoftBreakRenderer.kt +16 -0
  76. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +30 -27
  77. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BaseListSpan.kt +24 -0
  78. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +174 -24
  79. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +27 -3
  80. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBlockSpan.kt +13 -16
  81. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeSpan.kt +1 -1
  82. package/android/src/main/java/com/swmansion/enriched/markdown/spans/EmphasisSpan.kt +1 -1
  83. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +182 -46
  84. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ListItemSpacingSpan.kt +33 -0
  85. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ListMarkerAnchorSpan.kt +38 -0
  86. package/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt +2 -0
  87. package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrongSpan.kt +1 -1
  88. package/android/src/main/java/com/swmansion/enriched/markdown/spans/TaskListSpan.kt +3 -2
  89. package/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt +3 -2
  90. package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +6 -0
  91. package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +26 -0
  92. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +7 -1
  93. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +4 -1
  94. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +13 -4
  95. package/android/src/main/java/com/swmansion/enriched/markdown/styles/TableStyle.kt +6 -0
  96. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/CodeBlockHighlighter.kt +90 -0
  97. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/CodeBlockNode.kt +57 -0
  98. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/FeatureFlags.kt +1 -0
  99. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownSegment.kt +9 -0
  100. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +33 -7
  101. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/ReadableMapExtensions.kt +3 -0
  102. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/RenderedSegment.kt +10 -0
  103. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/SegmentSignature.kt +1 -0
  104. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/StreamingMarkdownFilter.kt +83 -5
  105. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/serialization/MarkdownASTSerializer.kt +4 -0
  106. package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/MarkdownStyleParser.kt +74 -44
  107. package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/PasteTextUtils.kt +20 -0
  108. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/ImageCache.kt +26 -3
  109. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/ImageDownloader.kt +22 -9
  110. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/LocalImageLoader.kt +161 -0
  111. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/extensions/TextPaintExtensions.kt +23 -0
  112. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/interaction/CheckboxTouchHelper.kt +7 -0
  113. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/interaction/TaskListTapUtils.kt +41 -21
  114. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/span/BlockSpacing.kt +15 -8
  115. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt +58 -27
  116. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +19 -2
  117. package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +388 -0
  118. package/android/src/main/java/com/swmansion/enriched/markdown/views/ContextMenuPopup.kt +9 -6
  119. package/android/src/main/java/com/swmansion/enriched/markdown/views/CopyGlyph.kt +101 -0
  120. package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +32 -5
  121. package/android/src/main/jni/CMakeLists.txt +53 -0
  122. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownContainerShadowNode.cpp +10 -0
  123. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownContainerShadowNode.h +11 -2
  124. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +10 -0
  125. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +12 -2
  126. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +9 -0
  127. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +24 -0
  128. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +16 -0
  129. package/android/src/math/java/com/swmansion/enriched/markdown/input/math/MathChipControllerImpl.kt +21 -4
  130. package/android/src/math/java/com/swmansion/enriched/markdown/renderer/MathInlineRenderer.kt +7 -2
  131. package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathChipSpan.kt +1 -1
  132. package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +2 -0
  133. package/cpp/highlight/CodeBlockHighlighter.cpp +284 -0
  134. package/cpp/highlight/CodeBlockHighlighter.hpp +66 -0
  135. package/cpp/highlight/CodeBlockLanguages.cpp +138 -0
  136. package/cpp/highlight/CodeBlockLanguages.hpp +25 -0
  137. package/cpp/highlight/HighlightGrammars.hpp +28 -0
  138. package/cpp/highlight/code_highlight_podspec.rb +211 -0
  139. package/cpp/highlight/gen-registry.mjs +155 -0
  140. package/cpp/highlight/grammar-versions.json +159 -0
  141. package/cpp/highlight/vendor/generated/generated_queries.h +1665 -0
  142. package/cpp/highlight/vendor/generated/generated_registry.cpp +59 -0
  143. package/cpp/parser/MD4CParser.cpp +173 -61
  144. package/cpp/parser/MD4CParser.hpp +1 -0
  145. package/cpp/parser/MarkdownASTNode.hpp +2 -1
  146. package/cpp/wasm/ASTSerializer.cpp +2 -0
  147. package/cpp/wasm/build.sh +1 -0
  148. package/cpp/wasm/md4c_wasm.cpp +3 -1
  149. package/docs/ACCESSIBILITY.md +14 -2
  150. package/docs/API_REFERENCE.md +222 -8
  151. package/docs/BREAKING_CHANGES.md +129 -0
  152. package/docs/CODE_HIGHLIGHT.md +133 -0
  153. package/docs/COPY_OPTIONS.md +21 -3
  154. package/docs/ELEMENTS_STRUCTURE.md +52 -4
  155. package/docs/IMAGE_CACHING.md +30 -0
  156. package/docs/INPUT.md +108 -11
  157. package/docs/LATEX_MATH.md +87 -45
  158. package/docs/MACOS.md +1 -1
  159. package/docs/MARKDOWN_STREAMING.md +27 -1
  160. package/docs/NATIVE_ASSETS.md +127 -0
  161. package/docs/RTL.md +1 -1
  162. package/docs/STYLES.md +46 -6
  163. package/docs/TESTING.md +112 -0
  164. package/docs/TEXT.md +16 -6
  165. package/docs/WEB.md +3 -1
  166. package/ios/EnrichedMarkdown.h +7 -0
  167. package/ios/EnrichedMarkdown.mm +262 -67
  168. package/ios/EnrichedMarkdownText.h +7 -0
  169. package/ios/EnrichedMarkdownText.mm +97 -68
  170. package/ios/attachments/ENRMImageAttachment.h +9 -0
  171. package/ios/attachments/ENRMImageAttachment.m +143 -40
  172. package/ios/attachments/ENRMMathInlineAttachment.h +2 -0
  173. package/ios/attachments/ENRMMathInlineAttachment.m +48 -81
  174. package/ios/attachments/ENRMMathInlineAttachmentShared.h +2 -0
  175. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +75 -0
  176. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +63 -0
  177. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +35 -0
  178. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +838 -23
  179. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +131 -0
  180. package/ios/input/ENRMAutoLinkDetector.h +2 -1
  181. package/ios/input/ENRMAutoLinkDetector.mm +79 -41
  182. package/ios/input/ENRMBlockEditCoordinator.h +71 -0
  183. package/ios/input/ENRMBlockEditCoordinator.mm +195 -0
  184. package/ios/input/ENRMBlockRange.h +28 -0
  185. package/ios/input/ENRMBlockRange.mm +30 -0
  186. package/ios/input/ENRMBlockStore.h +56 -0
  187. package/ios/input/ENRMBlockStore.mm +275 -0
  188. package/ios/input/ENRMClipboardCoordinator.h +45 -0
  189. package/ios/input/ENRMClipboardCoordinator.mm +120 -0
  190. package/ios/input/ENRMDetectorPipeline.h +2 -1
  191. package/ios/input/ENRMEditPipeline.h +76 -0
  192. package/ios/input/ENRMEditPipeline.mm +228 -0
  193. package/ios/input/ENRMEditSession.h +36 -0
  194. package/ios/input/ENRMEditSession.mm +63 -0
  195. package/ios/input/ENRMFormattingStore.h +17 -0
  196. package/ios/input/ENRMFormattingStore.mm +95 -105
  197. package/ios/input/ENRMInputBlockType.h +91 -0
  198. package/ios/input/ENRMInputBlockType.mm +5 -0
  199. package/ios/input/ENRMInputDecorationDrawer.h +18 -0
  200. package/ios/input/ENRMInputEventEmitter.h +56 -0
  201. package/ios/input/ENRMInputEventEmitter.mm +308 -0
  202. package/ios/input/ENRMInputFormatter.h +29 -69
  203. package/ios/input/ENRMInputFormatter.mm +216 -124
  204. package/ios/input/ENRMInputFormatterStyle.h +99 -0
  205. package/ios/input/ENRMInputFormatterStyle.mm +188 -0
  206. package/ios/input/ENRMInputLayoutManager.h +9 -0
  207. package/ios/input/ENRMInputLayoutManager.mm +30 -0
  208. package/ios/input/ENRMInputListMarkerDrawer.h +32 -0
  209. package/ios/input/ENRMInputListMarkerDrawer.mm +426 -0
  210. package/ios/input/ENRMInputParser.h +5 -6
  211. package/ios/input/ENRMInputParser.mm +461 -178
  212. package/ios/input/ENRMInputRemend.mm +2 -2
  213. package/ios/input/ENRMInputStyleStateBuilder.h +40 -0
  214. package/ios/input/ENRMInputStyleStateBuilder.mm +60 -0
  215. package/ios/input/ENRMInputTextView.mm +103 -8
  216. package/ios/input/ENRMInputTypingAttributesController.h +42 -0
  217. package/ios/input/ENRMInputTypingAttributesController.mm +203 -0
  218. package/ios/input/ENRMLinkCoordinator.h +41 -0
  219. package/ios/input/ENRMLinkCoordinator.mm +89 -0
  220. package/ios/input/ENRMMarkdownSerializer.h +29 -5
  221. package/ios/input/ENRMMarkdownSerializer.mm +170 -48
  222. package/ios/input/ENRMMathAtom.h +26 -0
  223. package/ios/input/ENRMMathAtom.mm +12 -0
  224. package/ios/input/ENRMMentionCoordinator.h +45 -0
  225. package/ios/input/ENRMMentionCoordinator.mm +167 -0
  226. package/ios/input/ENRMRangeEditAdjustment.h +38 -0
  227. package/ios/input/ENRMRangeEditAdjustment.mm +91 -0
  228. package/ios/input/ENRMRangeStoreUtils.h +39 -0
  229. package/ios/input/EnrichedMarkdownTextInput+ContextMenu.mm +6 -6
  230. package/ios/input/EnrichedMarkdownTextInput+Internal.h +11 -3
  231. package/ios/input/EnrichedMarkdownTextInput.h +7 -1
  232. package/ios/input/EnrichedMarkdownTextInput.mm +1089 -1023
  233. package/ios/input/InputStylePropsUtils.h +109 -68
  234. package/ios/input/internals/EnrichedMarkdownTextInputShadowNode.mm +2 -1
  235. package/ios/input/styles/ENRMBlockHandler.h +46 -0
  236. package/ios/input/styles/ENRMHeadingBlockHandler.h +15 -0
  237. package/ios/input/styles/ENRMHeadingBlockHandler.mm +38 -0
  238. package/ios/input/styles/ENRMOrderedListBlockHandler.h +14 -0
  239. package/ios/input/styles/ENRMOrderedListBlockHandler.mm +48 -0
  240. package/ios/input/styles/ENRMUnorderedListBlockHandler.h +15 -0
  241. package/ios/input/styles/ENRMUnorderedListBlockHandler.mm +51 -0
  242. package/ios/internals/ENRMViewFreeMeasurement.h +331 -0
  243. package/ios/internals/EnrichedMarkdownShadowNode.h +1 -3
  244. package/ios/internals/EnrichedMarkdownShadowNode.mm +16 -20
  245. package/ios/internals/EnrichedMarkdownTextShadowNode.h +1 -3
  246. package/ios/internals/EnrichedMarkdownTextShadowNode.mm +15 -20
  247. package/ios/internals/MeasurementCache.h +33 -7
  248. package/ios/internals/ShadowMeasurementUtils.h +63 -58
  249. package/ios/math/ENRMMathChipController.h +74 -0
  250. package/ios/math/ENRMMathChipController.mm +476 -0
  251. package/ios/math/ENRMRaTeXBridge.swift +4 -1
  252. package/ios/parser/ENRMMarkdownParser.h +1 -8
  253. package/ios/parser/ENRMMarkdownParser.mm +2 -11
  254. package/ios/parser/MarkdownASTNode.h +2 -1
  255. package/ios/parser/MarkdownParserBridge.mm +4 -0
  256. package/ios/renderer/AttributedRenderer.m +20 -2
  257. package/ios/renderer/BaseRenderer.h +17 -2
  258. package/ios/renderer/BaseRenderer.m +30 -0
  259. package/ios/renderer/BlockquoteRenderer.h +1 -1
  260. package/ios/renderer/BlockquoteRenderer.m +144 -35
  261. package/ios/renderer/CodeBlockRenderer.h +1 -1
  262. package/ios/renderer/CodeBlockRenderer.m +27 -23
  263. package/ios/renderer/CodeRenderer.h +1 -1
  264. package/ios/renderer/CodeRenderer.m +3 -1
  265. package/ios/renderer/ENRMImageRenderer.h +1 -1
  266. package/ios/renderer/ENRMImageRenderer.m +5 -1
  267. package/ios/renderer/ENRMMathInlineRenderer.h +1 -1
  268. package/ios/renderer/ENRMMathInlineRenderer.m +5 -1
  269. package/ios/renderer/ENRMSpoilerRenderer.h +1 -1
  270. package/ios/renderer/ENRMSpoilerRenderer.m +3 -1
  271. package/ios/renderer/EmphasisRenderer.h +1 -1
  272. package/ios/renderer/EmphasisRenderer.m +3 -1
  273. package/ios/renderer/HeadingRenderer.h +1 -1
  274. package/ios/renderer/HeadingRenderer.m +13 -6
  275. package/ios/renderer/HighlightRenderer.h +1 -1
  276. package/ios/renderer/HighlightRenderer.m +3 -1
  277. package/ios/renderer/LinkRenderer.h +1 -1
  278. package/ios/renderer/LinkRenderer.m +3 -1
  279. package/ios/renderer/ListItemRenderer.h +13 -1
  280. package/ios/renderer/ListItemRenderer.m +102 -44
  281. package/ios/renderer/ListRenderer.h +2 -2
  282. package/ios/renderer/ListRenderer.m +63 -25
  283. package/ios/renderer/ParagraphRenderer.h +1 -1
  284. package/ios/renderer/ParagraphRenderer.m +16 -8
  285. package/ios/renderer/RenderContext.h +10 -0
  286. package/ios/renderer/RenderContext.m +44 -0
  287. package/ios/renderer/RendererFactory.m +14 -0
  288. package/ios/renderer/StrikethroughRenderer.h +1 -1
  289. package/ios/renderer/StrikethroughRenderer.m +3 -1
  290. package/ios/renderer/StrongRenderer.h +1 -1
  291. package/ios/renderer/StrongRenderer.m +3 -1
  292. package/ios/renderer/SubscriptRenderer.h +1 -1
  293. package/ios/renderer/SubscriptRenderer.m +3 -1
  294. package/ios/renderer/SuperscriptRenderer.h +1 -1
  295. package/ios/renderer/SuperscriptRenderer.m +3 -1
  296. package/ios/renderer/TextRenderer.h +2 -2
  297. package/ios/renderer/TextRenderer.m +3 -1
  298. package/ios/renderer/ThematicBreakRenderer.h +1 -1
  299. package/ios/renderer/ThematicBreakRenderer.m +3 -1
  300. package/ios/renderer/UnderlineRenderer.h +1 -1
  301. package/ios/renderer/UnderlineRenderer.m +3 -1
  302. package/ios/styles/StyleConfig.h +22 -0
  303. package/ios/styles/StyleConfig.mm +132 -0
  304. package/ios/utils/BlockquoteBorder.h +6 -0
  305. package/ios/utils/BlockquoteBorder.m +249 -52
  306. package/ios/utils/CodeBlockBackground.m +16 -2
  307. package/ios/utils/ENRMAtomicSize.h +65 -0
  308. package/ios/utils/ENRMCodeBlockContent.h +37 -0
  309. package/ios/utils/ENRMCodeBlockContent.mm +79 -0
  310. package/ios/utils/ENRMCodeBlockHighlighter.h +37 -0
  311. package/ios/utils/ENRMCodeBlockHighlighter.mm +51 -0
  312. package/ios/utils/ENRMFeatureFlags.h +7 -0
  313. package/ios/utils/ENRMImageDownloader.h +11 -1
  314. package/ios/utils/ENRMImageDownloader.m +60 -16
  315. package/ios/utils/ENRMLocalImageLoader.h +31 -0
  316. package/ios/utils/ENRMLocalImageLoader.m +68 -0
  317. package/ios/utils/ENRMMathFallback.h +26 -0
  318. package/ios/utils/ENRMMathFallback.m +18 -0
  319. package/ios/utils/ENRMTextViewSetup.h +35 -17
  320. package/ios/utils/EditMenuUtils.m +22 -1
  321. package/ios/utils/ImageRequestHeaderUtils.h +35 -0
  322. package/ios/utils/LastElementUtils.h +1 -0
  323. package/ios/utils/ListMarkerDrawer.m +29 -42
  324. package/ios/utils/MarkdownASTSerializer.m +4 -0
  325. package/ios/utils/ParagraphStyleUtils.h +5 -0
  326. package/ios/utils/ParagraphStyleUtils.m +77 -17
  327. package/ios/utils/RenderedMarkdownSegment.h +13 -1
  328. package/ios/utils/RenderedMarkdownSegment.m +20 -0
  329. package/ios/utils/SegmentRenderer.m +17 -4
  330. package/ios/utils/StreamingMarkdownFilter.h +11 -1
  331. package/ios/utils/StreamingMarkdownFilter.m +98 -2
  332. package/ios/utils/StylePropsUtils.h +63 -0
  333. package/ios/utils/TaskListTapUtils.m +24 -0
  334. package/ios/views/ENRMCodeBlockContainerView.h +46 -0
  335. package/ios/views/ENRMCodeBlockContainerView.m +566 -0
  336. package/ios/views/ENRMMathContainerView.h +12 -0
  337. package/ios/views/ENRMMathContainerView.m +71 -1
  338. package/ios/views/TableContainerView.h +18 -0
  339. package/ios/views/TableContainerView.m +259 -159
  340. package/jest.d.ts +1 -0
  341. package/jest.js +5 -0
  342. package/lib/module/EnrichedMarkdownNativeComponent.ts +65 -0
  343. package/lib/module/EnrichedMarkdownTextInput.js +84 -5
  344. package/lib/module/EnrichedMarkdownTextInput.js.map +1 -1
  345. package/lib/module/EnrichedMarkdownTextInputNativeComponent.ts +78 -5
  346. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +66 -0
  347. package/lib/module/headingDefaults.js +34 -0
  348. package/lib/module/headingDefaults.js.map +1 -0
  349. package/lib/module/index.js.map +1 -1
  350. package/lib/module/jest/index.js +191 -0
  351. package/lib/module/jest/index.js.map +1 -0
  352. package/lib/module/native/EnrichedMarkdownText.js +30 -4
  353. package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
  354. package/lib/module/normalizeMarkdownStyle.js +81 -19
  355. package/lib/module/normalizeMarkdownStyle.js.map +1 -1
  356. package/lib/module/normalizeMarkdownStyle.web.js +56 -18
  357. package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
  358. package/lib/module/normalizeMarkdownTextInputStyle.js +62 -15
  359. package/lib/module/normalizeMarkdownTextInputStyle.js.map +1 -1
  360. package/lib/module/normalizeMenuItem.js +1 -1
  361. package/lib/module/utils/textInputState.js +26 -0
  362. package/lib/module/utils/textInputState.js.map +1 -0
  363. package/lib/module/web/EnrichedMarkdownText.js +9 -5
  364. package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
  365. package/lib/module/web/parseMarkdown.js +4 -3
  366. package/lib/module/web/parseMarkdown.js.map +1 -1
  367. package/lib/module/web/renderers/InlineRenderers.js +6 -0
  368. package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
  369. package/lib/module/web/renderers/ListRenderers.js +7 -2
  370. package/lib/module/web/renderers/ListRenderers.js.map +1 -1
  371. package/lib/module/web/renderers/index.js +7 -4
  372. package/lib/module/web/renderers/index.js.map +1 -1
  373. package/lib/module/web/styles.js +83 -6
  374. package/lib/module/web/styles.js.map +1 -1
  375. package/lib/module/web/utils.js +1 -0
  376. package/lib/module/web/utils.js.map +1 -1
  377. package/lib/module/web/wasm/md4c.js +0 -0
  378. package/lib/module/web/wasm/md4c.js.map +1 -1
  379. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +62 -0
  380. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
  381. package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts +94 -9
  382. package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts.map +1 -1
  383. package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts +69 -5
  384. package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts.map +1 -1
  385. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +62 -0
  386. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  387. package/lib/typescript/src/headingDefaults.d.ts +9 -0
  388. package/lib/typescript/src/headingDefaults.d.ts.map +1 -0
  389. package/lib/typescript/src/index.d.ts +2 -2
  390. package/lib/typescript/src/index.d.ts.map +1 -1
  391. package/lib/typescript/src/index.web.d.ts +1 -1
  392. package/lib/typescript/src/index.web.d.ts.map +1 -1
  393. package/lib/typescript/src/jest/index.d.ts +6 -0
  394. package/lib/typescript/src/jest/index.d.ts.map +1 -0
  395. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +3 -3
  396. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
  397. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
  398. package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
  399. package/lib/typescript/src/normalizeMarkdownTextInputStyle.d.ts +19 -5
  400. package/lib/typescript/src/normalizeMarkdownTextInputStyle.d.ts.map +1 -1
  401. package/lib/typescript/src/types/MarkdownStyle.d.ts +72 -0
  402. package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
  403. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +27 -0
  404. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
  405. package/lib/typescript/src/types/MarkdownTextProps.d.ts +65 -1
  406. package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
  407. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +19 -0
  408. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
  409. package/lib/typescript/src/types/events.d.ts +19 -0
  410. package/lib/typescript/src/types/events.d.ts.map +1 -1
  411. package/lib/typescript/src/utils/textInputState.d.ts +24 -0
  412. package/lib/typescript/src/utils/textInputState.d.ts.map +1 -0
  413. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
  414. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
  415. package/lib/typescript/src/web/parseMarkdown.d.ts +1 -1
  416. package/lib/typescript/src/web/parseMarkdown.d.ts.map +1 -1
  417. package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
  418. package/lib/typescript/src/web/renderers/ListRenderers.d.ts.map +1 -1
  419. package/lib/typescript/src/web/renderers/index.d.ts +2 -1
  420. package/lib/typescript/src/web/renderers/index.d.ts.map +1 -1
  421. package/lib/typescript/src/web/styles.d.ts +2 -1
  422. package/lib/typescript/src/web/styles.d.ts.map +1 -1
  423. package/lib/typescript/src/web/types.d.ts +6 -2
  424. package/lib/typescript/src/web/types.d.ts.map +1 -1
  425. package/lib/typescript/src/web/utils.d.ts.map +1 -1
  426. package/package.json +36 -9
  427. package/postinstall.mjs +131 -0
  428. package/ratex-version.json +21 -0
  429. package/src/EnrichedMarkdownNativeComponent.ts +65 -0
  430. package/src/EnrichedMarkdownTextInput.tsx +159 -8
  431. package/src/EnrichedMarkdownTextInputNativeComponent.ts +78 -5
  432. package/src/EnrichedMarkdownTextNativeComponent.ts +66 -0
  433. package/src/headingDefaults.ts +21 -0
  434. package/src/index.tsx +9 -0
  435. package/src/index.web.tsx +1 -0
  436. package/src/jest/index.tsx +215 -0
  437. package/src/native/EnrichedMarkdownText.tsx +40 -2
  438. package/src/normalizeMarkdownStyle.ts +102 -16
  439. package/src/normalizeMarkdownStyle.web.ts +63 -15
  440. package/src/normalizeMarkdownTextInputStyle.ts +110 -35
  441. package/src/normalizeMenuItem.ts +1 -1
  442. package/src/types/MarkdownStyle.ts +74 -0
  443. package/src/types/MarkdownStyleInternal.ts +39 -0
  444. package/src/types/MarkdownTextProps.ts +65 -0
  445. package/src/types/MarkdownTextProps.web.ts +19 -0
  446. package/src/types/events.ts +21 -0
  447. package/src/utils/textInputState.ts +35 -0
  448. package/src/web/EnrichedMarkdownText.tsx +16 -2
  449. package/src/web/parseMarkdown.ts +6 -2
  450. package/src/web/renderers/InlineRenderers.tsx +5 -0
  451. package/src/web/renderers/ListRenderers.tsx +14 -2
  452. package/src/web/renderers/index.tsx +6 -2
  453. package/src/web/styles.ts +89 -5
  454. package/src/web/types.ts +6 -1
  455. package/src/web/utils.ts +1 -0
  456. package/src/web/wasm/md4c.js +0 -0
  457. package/vendor-grammars.mjs +511 -0
  458. package/vendor-ratex.mjs +173 -0
  459. package/app.plugin.js +0 -2
  460. package/plugin/build/withAndroidMath.js +0 -19
  461. package/plugin/build/withIosMath.js +0 -28
  462. package/plugin/build/withReactNativeEnrichedMarkdown.js +0 -11
  463. package/plugin/src/withAndroidMath.ts +0 -24
  464. package/plugin/src/withIosMath.ts +0 -35
  465. package/plugin/src/withReactNativeEnrichedMarkdown.ts +0 -17
  466. package/plugin/tsconfig.build.json +0 -16
@@ -1,14 +1,17 @@
1
1
  #import "ENRMInputParser.h"
2
2
  #import "ENRMBlockSyntaxSentinel.h"
3
+ #import "ENRMFeatureFlags.h"
3
4
  #import "ENRMFormattingRange.h"
4
5
  #import "ENRMInputRemend.h"
5
6
  #include "md4c.h"
7
+ #include <algorithm>
6
8
  #include <string>
7
9
  #include <vector>
8
10
 
9
11
  @interface ENRMParseResult ()
10
12
  @property (nonatomic, strong, readwrite) NSString *plainText;
11
13
  @property (nonatomic, strong, readwrite) NSArray<ENRMFormattingRange *> *formattingRanges;
14
+ @property (nonatomic, strong, readwrite) NSArray<ENRMBlockRange *> *blockRanges;
12
15
  @property (nonatomic, strong, readwrite) NSArray<ENRMMathAtom *> *mathAtoms;
13
16
  @end
14
17
 
@@ -20,9 +23,6 @@
20
23
  - (ENRMParseResult *)parseToPlainTextAndRanges:(NSString *)markdown remend:(BOOL)remend;
21
24
  @end
22
25
 
23
- @implementation ENRMMathAtom
24
- @end
25
-
26
26
  namespace {
27
27
 
28
28
  static const size_t kByteOffsetUnset = SIZE_MAX;
@@ -50,6 +50,46 @@ static bool isSupportedSpan(MD_SPANTYPE md4cType, ENRMInputStyleType &outStyleTy
50
50
  return false;
51
51
  }
52
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
+
53
93
  struct InlineSpanInfo {
54
94
  ENRMInputStyleType type;
55
95
  size_t openingDelimiterByteOffset;
@@ -58,11 +98,38 @@ struct InlineSpanInfo {
58
98
  std::string linkURL;
59
99
  };
60
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).
61
114
  struct MathSpanInfo {
62
115
  bool displayMode;
63
- size_t openingDelimiterByteOffset;
116
+ size_t contentStartByteOffset = kByteOffsetUnset;
64
117
  size_t contentEndByteOffset = kByteOffsetUnset;
65
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
+ }
66
133
  };
67
134
 
68
135
  struct ParseContext {
@@ -71,9 +138,16 @@ struct ParseContext {
71
138
  size_t originalLength;
72
139
  std::vector<InlineSpanInfo> openStack;
73
140
  std::vector<InlineSpanInfo> resolved;
141
+ std::vector<BlockInfo> openBlockStack;
142
+ std::vector<BlockInfo> resolvedBlocks;
74
143
  std::vector<MathSpanInfo> openMathStack;
75
144
  std::vector<MathSpanInfo> resolvedMath;
145
+ std::vector<size_t> textStartOffsets;
76
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;
77
151
  };
78
152
 
79
153
  static std::vector<NSUInteger> buildByteToUTF16Map(const char *utf8, size_t byteLength)
@@ -114,55 +188,119 @@ static inline NSUInteger mapByteOffset(const std::vector<NSUInteger> &map, size_
114
188
  return map[std::min(offset, maxOffset)];
115
189
  }
116
190
 
117
- static const size_t kClosingDelimiterByteLength[] = {
118
- [ENRMInputStyleTypeStrong] = 2, [ENRMInputStyleTypeEmphasis] = 1, [ENRMInputStyleTypeUnderline] = 1,
119
- [ENRMInputStyleTypeStrikethrough] = 2, [ENRMInputStyleTypeSpoiler] = 2,
120
- };
121
-
122
- static size_t closingDelimiterEndByte(const InlineSpanInfo &span, const char *utf8, size_t bufferLength)
191
+ static size_t closingDelimiterEndByte(const ParseContext &context, const InlineSpanInfo &span)
123
192
  {
124
193
  size_t position = span.contentEndByteOffset;
125
194
 
126
195
  if (span.type == ENRMInputStyleTypeLink) {
127
- while (position < bufferLength && utf8[position] != ')') {
196
+ while (position < context.bufferLength && context.buffer[position] != ')') {
128
197
  position++;
129
198
  }
130
- return (position < bufferLength) ? position + 1 : position;
199
+ return (position < context.bufferLength) ? position + 1 : position;
131
200
  }
132
201
 
133
- 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;
134
208
  }
135
209
 
136
- // TODO: onEnterBlock/onLeaveBlock are no-ops, so lastTextEnd never advances
137
- // past inter-block whitespace. This causes onEnterSpan to set
138
- // openingDelimiterByteOffset to a position that includes \n\n (or other
139
- // block-separator characters) in the syntax range. The newline-stripping
140
- // workaround in parseToPlainTextAndRanges only handles \n/\r — it won't
141
- // catch block-level syntax like list markers, blockquote '>', or heading '#'
142
- // if those elements are added in the future.
143
- //
144
- // A proper fix would advance lastTextEnd here (or retroactively adjust
145
- // openingDelimiterByteOffset for spans opened since the last block
146
- // transition). md4c's enter_block doesn't provide a byte offset, so this
147
- // likely requires tracking a "new block" flag and correcting span offsets
148
- // in onText when the first text inside a new block arrives.
149
- 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)
150
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);
151
254
  return 0;
152
255
  }
153
- static int onLeaveBlock(MD_BLOCKTYPE, void *, void *)
256
+
257
+ static int onLeaveBlock(MD_BLOCKTYPE blockType, void *, void *userdata)
154
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();
155
289
  return 0;
156
290
  }
157
291
 
292
+ static inline bool isMathSpan(MD_SPANTYPE spanType)
293
+ {
294
+ return spanType == MD_SPAN_LATEXMATH || spanType == MD_SPAN_LATEXMATH_DISPLAY;
295
+ }
296
+
158
297
  static int onEnterSpan(MD_SPANTYPE spanType, void *detail, void *userdata)
159
298
  {
160
299
  auto *context = static_cast<ParseContext *>(userdata);
161
300
 
162
- if (spanType == MD_SPAN_LATEXMATH || spanType == MD_SPAN_LATEXMATH_DISPLAY) {
301
+ if (isMathSpan(spanType)) {
163
302
  MathSpanInfo mathInfo;
164
303
  mathInfo.displayMode = (spanType == MD_SPAN_LATEXMATH_DISPLAY);
165
- mathInfo.openingDelimiterByteOffset = context->lastTextEnd;
166
304
  context->openMathStack.push_back(mathInfo);
167
305
  return 0;
168
306
  }
@@ -191,7 +329,7 @@ static int onLeaveSpan(MD_SPANTYPE spanType, void *, void *userdata)
191
329
  {
192
330
  auto *context = static_cast<ParseContext *>(userdata);
193
331
 
194
- if (spanType == MD_SPAN_LATEXMATH || spanType == MD_SPAN_LATEXMATH_DISPLAY) {
332
+ if (isMathSpan(spanType)) {
195
333
  if (context->openMathStack.empty()) {
196
334
  return 0;
197
335
  }
@@ -214,25 +352,56 @@ static int onLeaveSpan(MD_SPANTYPE spanType, void *, void *userdata)
214
352
  return 0;
215
353
  }
216
354
 
217
- 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)
218
356
  {
219
357
  if (!text || size == 0) {
220
358
  return 0;
221
359
  }
222
360
  auto *context = static_cast<ParseContext *>(userdata);
223
361
 
224
- // Inside a math span, md4c emits the latex source (typically MD_TEXT_LATEXMATH).
225
- // 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.
226
368
  if (!context->openMathStack.empty()) {
227
- (void)textType;
228
- MathSpanInfo &top = context->openMathStack.back();
229
- top.latex.append(text, size);
369
+ MathSpanInfo &math = context->openMathStack.back();
370
+ math.latex.append(text, size);
371
+
230
372
  bool insideBuffer = (text >= context->buffer && text < context->buffer + context->bufferLength);
231
- if (insideBuffer) {
232
- size_t textStart = text - context->buffer;
233
- top.contentEndByteOffset = textStart + size;
234
- 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;
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;
235
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;
236
405
  return 0;
237
406
  }
238
407
 
@@ -240,10 +409,9 @@ static int onText(MD_TEXTTYPE textType, const MD_CHAR *text, MD_SIZE size, void
240
409
  // (e.g. MD_TEXT_SOFTBR, MD_TEXT_BR use a string literal "\n").
241
410
  // Pointer arithmetic against context->buffer would underflow, corrupting offsets.
242
411
  //
243
- // TODO: Skipping out-of-buffer tokens means lastTextEnd doesn't advance past
244
- // newlines. Fine for inline spans, but if block-level input formatting is
245
- // added later, openingDelimiterByteOffset will include newline chars in the
246
- // 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.
247
415
  bool insideBuffer = (text >= context->buffer && text < context->buffer + context->bufferLength);
248
416
  if (!insideBuffer) {
249
417
  return 0;
@@ -251,6 +419,7 @@ static int onText(MD_TEXTTYPE textType, const MD_CHAR *text, MD_SIZE size, void
251
419
 
252
420
  size_t textStart = text - context->buffer;
253
421
  size_t textEnd = textStart + size;
422
+ context->textStartOffsets.push_back(textStart);
254
423
 
255
424
  for (auto &openSpan : context->openStack) {
256
425
  if (openSpan.contentStartByteOffset == kByteOffsetUnset) {
@@ -258,10 +427,26 @@ static int onText(MD_TEXTTYPE textType, const MD_CHAR *text, MD_SIZE size, void
258
427
  }
259
428
  openSpan.contentEndByteOffset = textEnd;
260
429
  }
430
+ for (auto &openBlock : context->openBlockStack) {
431
+ if (openBlock.contentStartByteOffset == kByteOffsetUnset) {
432
+ openBlock.contentStartByteOffset = textStart;
433
+ }
434
+ openBlock.contentEndByteOffset = textEnd;
435
+ }
261
436
  context->lastTextEnd = textEnd;
262
437
  return 0;
263
438
  }
264
439
 
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
+
265
450
  static bool runMd4cParse(NSString *markdown, ParseContext &context, bool remend)
266
451
  {
267
452
  NSString *completed = remend ? ENRMInputRemendComplete(markdown) : markdown;
@@ -275,7 +460,7 @@ static bool runMd4cParse(NSString *markdown, ParseContext &context, bool remend)
275
460
 
276
461
  MD_PARSER parser = {
277
462
  .abi_version = 0,
278
- .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,
279
464
  .enter_block = onEnterBlock,
280
465
  .leave_block = onLeaveBlock,
281
466
  .enter_span = onEnterSpan,
@@ -288,25 +473,12 @@ static bool runMd4cParse(NSString *markdown, ParseContext &context, bool remend)
288
473
  return md_parse(completedUTF8, (MD_SIZE)completedLength, &parser, &context) == 0;
289
474
  }
290
475
 
291
- struct ParsedSpans {
292
- std::vector<MathSpanInfo> mathSpans;
293
- };
294
-
295
- static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, ParsedSpans &out, bool remend)
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)
296
481
  {
297
- if (markdown.length == 0) {
298
- return @[];
299
- }
300
-
301
- ParseContext context;
302
- if (!runMd4cParse(markdown, context, remend)) {
303
- return @[];
304
- }
305
-
306
- out.mathSpans = context.resolvedMath;
307
-
308
- auto byteMap = buildByteToUTF16Map(context.buffer, context.bufferLength);
309
-
310
482
  NSMutableArray<ENRMInputStyledRange *> *results = [NSMutableArray arrayWithCapacity:context.resolved.size()];
311
483
 
312
484
  for (const auto &spanInfo : context.resolved) {
@@ -329,8 +501,7 @@ static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, Par
329
501
  NSUInteger openStart = mapByteOffset(byteMap, spanInfo.openingDelimiterByteOffset, context.bufferLength);
330
502
  NSRange openingRange = NSMakeRange(openStart, contentStart - openStart);
331
503
 
332
- size_t closingEndByte =
333
- std::min(closingDelimiterEndByte(spanInfo, context.buffer, context.bufferLength), context.bufferLength);
504
+ size_t closingEndByte = std::min(closingDelimiterEndByte(context, spanInfo), context.bufferLength);
334
505
  NSUInteger closingStart = mapByteOffset(byteMap, spanInfo.contentEndByteOffset, context.bufferLength);
335
506
  NSUInteger closingEnd = mapByteOffset(byteMap, closingEndByte, context.bufferLength);
336
507
  NSRange closingRange = NSMakeRange(closingStart, closingEnd - closingStart);
@@ -354,14 +525,121 @@ static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, Par
354
525
  return results;
355
526
  }
356
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
+
357
626
  } // namespace
358
627
 
359
628
  @implementation ENRMInputParser
360
629
 
361
630
  - (NSArray<ENRMInputStyledRange *> *)parse:(NSString *)markdown
362
631
  {
363
- ParsedSpans spans;
364
- return runParseInternal(markdown, spans, /* remend */ true);
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);
365
643
  }
366
644
 
367
645
  - (ENRMParseResult *)parseToPlainTextAndRanges:(NSString *)markdown
@@ -375,13 +653,19 @@ static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, Par
375
653
  return [self parseToPlainTextAndRanges:markdown];
376
654
  }
377
655
 
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
+
378
663
  NSString *canonical = ENRMBlockSyntaxCanonicalize(markdown);
379
664
  if (canonical.length == 0) {
380
- ENRMParseResult *empty = [[ENRMParseResult alloc] init];
381
- empty.plainText = @"";
382
- empty.formattingRanges = @[];
383
- empty.mathAtoms = @[];
384
- return empty;
665
+ result.plainText = @"";
666
+ result.formattingRanges = @[];
667
+ result.mathAtoms = @[];
668
+ return result;
385
669
  }
386
670
 
387
671
  // Remending is skipped too: its speculative closing delimiters are the other
@@ -389,8 +673,6 @@ static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, Par
389
673
  ENRMParseResult *encoded = [self parseToPlainTextAndRanges:ENRMBlockSyntaxEncode(canonical) remend:NO];
390
674
 
391
675
  NSString *decoded = ENRMBlockSyntaxDecode(encoded.plainText);
392
- ENRMParseResult *result = [[ENRMParseResult alloc] init];
393
-
394
676
  if (decoded == nil) {
395
677
  result.plainText = canonical;
396
678
  result.formattingRanges = @[];
@@ -438,58 +720,26 @@ static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, Par
438
720
  if (markdown.length == 0) {
439
721
  parseResult.plainText = @"";
440
722
  parseResult.formattingRanges = @[];
723
+ parseResult.blockRanges = @[];
441
724
  parseResult.mathAtoms = @[];
442
725
  return parseResult;
443
726
  }
444
727
 
445
- ParsedSpans parsedSpans;
446
- NSArray<ENRMInputStyledRange *> *styledRanges = runParseInternal(markdown, parsedSpans, remend);
447
-
448
- // Compute byte map + raw byte ranges for math spans so we can strip the
449
- // entire `$...$` (or `$$...$$`) syntax and inject a single `` at each.
450
- size_t rawByteLength = [markdown lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
451
- const char *rawUTF8 = [markdown UTF8String];
452
- std::vector<NSUInteger> rawByteMap = buildByteToUTF16Map(rawUTF8, rawByteLength);
728
+ NSUInteger rawLength = markdown.length;
453
729
 
454
- struct MathChip {
455
- NSRange rawSyntaxRange; // UTF-16 range in the original markdown to strip
456
- NSString *latex;
457
- BOOL displayMode;
458
- };
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 = @[];
459
735
  std::vector<MathChip> mathChips;
460
- mathChips.reserve(parsedSpans.mathSpans.size());
461
- for (const auto &mathInfo : parsedSpans.mathSpans) {
462
- if (mathInfo.contentEndByteOffset == kByteOffsetUnset)
463
- continue;
464
- size_t closingByteEnd = mathInfo.contentEndByteOffset + (mathInfo.displayMode ? 2 : 1);
465
- if (closingByteEnd > rawByteLength)
466
- closingByteEnd = rawByteLength;
467
- if (mathInfo.openingDelimiterByteOffset > rawByteLength)
468
- continue;
469
- if (closingByteEnd > rawByteLength)
470
- continue;
471
-
472
- NSUInteger rawStart = rawByteMap[mathInfo.openingDelimiterByteOffset];
473
- NSUInteger rawEnd = rawByteMap[closingByteEnd];
474
- if (rawEnd <= rawStart)
475
- continue;
476
-
477
- MathChip chip;
478
- chip.rawSyntaxRange = NSMakeRange(rawStart, rawEnd - rawStart);
479
- chip.latex = [[NSString alloc] initWithBytes:mathInfo.latex.data()
480
- length:mathInfo.latex.size()
481
- encoding:NSUTF8StringEncoding]
482
- ?: @"";
483
- chip.displayMode = mathInfo.displayMode ? YES : NO;
484
- mathChips.push_back(chip);
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);
485
741
  }
486
742
 
487
- // Sort math chips by raw position so we process left-to-right.
488
- std::sort(mathChips.begin(), mathChips.end(),
489
- [](const MathChip &a, const MathChip &b) { return a.rawSyntaxRange.location < b.rawSyntaxRange.location; });
490
-
491
- NSUInteger rawLength = markdown.length;
492
-
493
743
  NSMutableIndexSet *syntaxIndexes = [NSMutableIndexSet indexSet];
494
744
  for (ENRMInputStyledRange *styledRange in styledRanges) {
495
745
  if (!styledRange.isComplete)
@@ -505,19 +755,28 @@ static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, Par
505
755
  }
506
756
  }
507
757
 
508
- // Add math full ranges (delimiters + latex) to the strip set.
509
- for (const auto &chip : mathChips) {
510
- NSRange r = chip.rawSyntaxRange;
511
- if (NSMaxRange(r) > rawLength) {
512
- 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;
513
764
  }
514
- if (r.length > 0) {
515
- [syntaxIndexes addIndexesInRange:r];
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--;
772
+ }
773
+ if (contentStart > lineStart) {
774
+ [syntaxIndexes addIndexesInRange:NSMakeRange(lineStart, contentStart - lineStart)];
516
775
  }
517
776
  }
518
777
 
519
778
  // Strip \n/\r from syntax ranges — newlines are structural content, not
520
- // markdown syntax (included due to no-op onEnterBlock/onLeaveBlock).
779
+ // markdown syntax, and must survive into the plain text.
521
780
  NSMutableIndexSet *newlineIndexes = [NSMutableIndexSet indexSet];
522
781
  [syntaxIndexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) {
523
782
  if (index < rawLength) {
@@ -529,32 +788,73 @@ static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, Par
529
788
  }];
530
789
  [syntaxIndexes removeIndexes:newlineIndexes];
531
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.
532
801
  NSMutableString *plainText = [NSMutableString stringWithCapacity:rawLength];
533
- __block std::vector<NSUInteger> rawToPlainMap(rawLength + 1, 0);
802
+ std::vector<NSUInteger> rawToPlainMap(rawLength + 1, 0);
803
+ NSMutableArray<ENRMMathAtom *> *mathAtoms = [NSMutableArray arrayWithCapacity:mathChips.size()];
804
+
805
+ NSUInteger plainPosition = 0;
806
+ NSUInteger verbatimStart = 0;
807
+ size_t nextChip = 0;
534
808
 
535
- __block NSUInteger plainPosition = 0;
536
- __block NSUInteger previousEnd = 0;
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
+ };
537
819
 
538
- [syntaxIndexes enumerateRangesUsingBlock:^(NSRange syntaxRange, BOOL *stop) {
539
- if (syntaxRange.location > previousEnd) {
540
- NSRange visibleRange = NSMakeRange(previousEnd, syntaxRange.location - previousEnd);
541
- [plainText appendString:[markdown substringWithRange:visibleRange]];
542
- for (NSUInteger rawIndex = previousEnd; rawIndex < syntaxRange.location; rawIndex++) {
543
- 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;
544
838
  }
839
+ plainPosition++;
840
+
841
+ rawIndex = chipEnd;
842
+ verbatimStart = rawIndex;
843
+ nextChip++;
844
+ continue;
545
845
  }
546
- for (NSUInteger rawIndex = syntaxRange.location; rawIndex < NSMaxRange(syntaxRange); rawIndex++) {
846
+
847
+ if ([syntaxIndexes containsIndex:rawIndex]) {
848
+ flushVerbatim(verbatimStart, rawIndex);
547
849
  rawToPlainMap[rawIndex] = plainPosition;
850
+ rawIndex++;
851
+ verbatimStart = rawIndex;
852
+ continue;
548
853
  }
549
- previousEnd = NSMaxRange(syntaxRange);
550
- }];
551
854
 
552
- if (previousEnd < rawLength) {
553
- [plainText appendString:[markdown substringFromIndex:previousEnd]];
554
- for (NSUInteger rawIndex = previousEnd; rawIndex < rawLength; rawIndex++) {
555
- rawToPlainMap[rawIndex] = plainPosition++;
556
- }
855
+ rawIndex++;
557
856
  }
857
+ flushVerbatim(verbatimStart, rawLength);
558
858
  rawToPlainMap[rawLength] = plainPosition;
559
859
 
560
860
  NSMutableArray<ENRMFormattingRange *> *formattingRanges = [NSMutableArray arrayWithCapacity:styledRanges.count];
@@ -582,46 +882,29 @@ static NSArray<ENRMInputStyledRange *> *runParseInternal(NSString *markdown, Par
582
882
  [formattingRanges addObject:formattingRange];
583
883
  }
584
884
 
585
- // Inject `` (U+FFFC, NSAttachmentCharacter) at each math chip's plain
586
- // position and emit ENRMMathAtom records pointing at the inserted character.
587
- NSMutableArray<ENRMMathAtom *> *mathAtoms = [NSMutableArray arrayWithCapacity:mathChips.size()];
588
- static unichar attachmentChar = (unichar)0xFFFC;
589
- NSString *attachmentString = [NSString stringWithCharacters:&attachmentChar length:1];
590
- NSUInteger insertionsSoFar = 0;
591
- for (const auto &chip : mathChips) {
592
- NSUInteger rawStart = chip.rawSyntaxRange.location;
593
- 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)
594
898
  continue;
595
- NSUInteger basePlain = rawToPlainMap[rawStart];
596
- NSUInteger insertAt = basePlain + insertionsSoFar;
597
- if (insertAt > plainText.length)
598
- insertAt = plainText.length;
599
- [plainText insertString:attachmentString atIndex:insertAt];
600
-
601
- ENRMMathAtom *atom = [[ENRMMathAtom alloc] init];
602
- atom.position = insertAt;
603
- atom.latex = chip.latex ?: @"";
604
- atom.displayMode = chip.displayMode;
605
- [mathAtoms addObject:atom];
606
-
607
- // Shift existing formatting ranges that start at or after this insertion.
608
- for (NSUInteger idx = 0; idx < formattingRanges.count; idx++) {
609
- ENRMFormattingRange *fr = formattingRanges[idx];
610
- NSRange r = fr.range;
611
- if (r.location >= insertAt) {
612
- r.location += 1;
613
- formattingRanges[idx] = [ENRMFormattingRange rangeWithType:fr.type range:r url:fr.url];
614
- } else if (NSMaxRange(r) > insertAt) {
615
- r.length += 1;
616
- formattingRanges[idx] = [ENRMFormattingRange rangeWithType:fr.type range:r url:fr.url];
617
- }
618
- }
619
899
 
620
- insertionsSoFar++;
900
+ [blockRanges addObject:[ENRMBlockRange rangeWithType:rawBlock.type
901
+ range:NSMakeRange(plainStart, plainEnd - plainStart)
902
+ level:rawBlock.level]];
621
903
  }
622
904
 
623
905
  parseResult.plainText = plainText;
624
906
  parseResult.formattingRanges = formattingRanges;
907
+ parseResult.blockRanges = blockRanges;
625
908
  parseResult.mathAtoms = mathAtoms;
626
909
  return parseResult;
627
910
  }