react-native-marcus 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/README.md +220 -63
  2. package/RNMarcus.podspec +6 -0
  3. package/android/src/main/java/app/getbullet/marcus/MarcusPackage.kt +2 -1
  4. package/android/src/main/java/app/getbullet/marcus/MarcusTextDecoratorView.kt +168 -0
  5. package/android/src/main/java/app/getbullet/marcus/MarcusTextDecoratorViewManager.kt +38 -0
  6. package/android/src/main/java/app/getbullet/marcus/MarcusTextDecoratorViewManagerSpec.kt +19 -0
  7. package/android/src/main/java/app/getbullet/marcus/MarcusTextInputDecoratorView.kt +23 -1
  8. package/android/src/main/java/app/getbullet/marcus/MarkdownBackgroundPainter.kt +147 -0
  9. package/android/src/main/java/app/getbullet/marcus/MarkdownBackgroundPass.kt +19 -0
  10. package/android/src/main/java/app/getbullet/marcus/MarkdownCopy.kt +87 -0
  11. package/android/src/main/java/app/getbullet/marcus/MarkdownFormatter.kt +652 -113
  12. package/android/src/main/java/app/getbullet/marcus/MarkdownRanges.kt +47 -0
  13. package/android/src/main/java/app/getbullet/marcus/MarkdownStyle.kt +140 -61
  14. package/android/src/main/java/app/getbullet/marcus/MarkdownUtils.kt +37 -7
  15. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownAbsoluteLineHeightSpan.kt +48 -0
  16. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBackgroundSpan.kt +102 -48
  17. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBlockIndentSpan.kt +22 -4
  18. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBulletSpan.kt +78 -0
  19. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownCodeBlockSpan.kt +177 -0
  20. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownFontStyleSpan.kt +32 -0
  21. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownFontWeightSpan.kt +34 -0
  22. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownGapSpan.kt +24 -11
  23. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownLetterSpacingSpan.kt +32 -0
  24. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownTextShadowSpan.kt +32 -0
  25. package/android/src/main/new_arch/CMakeLists.txt +17 -0
  26. package/android/src/main/new_arch/RNMarcusSpec.h +1 -0
  27. package/android/src/main/new_arch/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorShadowNode.cpp +235 -0
  28. package/android/src/main/new_arch/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorShadowNode.h +62 -0
  29. package/android/src/reactNativeVersionPatch/CustomMountingManager/81/app/getbullet/marcus/CustomFabricUIManager.java +22 -3
  30. package/android/src/reactNativeVersionPatch/CustomMountingManager/latest/app/getbullet/marcus/CustomFabricUIManager.java +22 -3
  31. package/android/src/test/java/app/getbullet/marcus/MarkdownCopyTest.kt +69 -0
  32. package/android/src/test/java/app/getbullet/marcus/MarkdownFormatterTest.kt +170 -25
  33. package/android/src/test/java/app/getbullet/marcus/RenderModel.kt +22 -5
  34. package/android/src/test/java/app/getbullet/marcus/spans/MarkdownCodeBlockSpanTest.kt +91 -0
  35. package/android/src/test/resources/render-model.txt +221 -16
  36. package/apple/MarcusSwizzle.h +25 -0
  37. package/apple/MarcusTextDecoratorComponentView.h +22 -0
  38. package/apple/MarcusTextDecoratorComponentView.mm +72 -0
  39. package/apple/MarcusTextDecoratorShadowNode.h +62 -0
  40. package/apple/MarcusTextDecoratorShadowNode.mm +192 -0
  41. package/apple/MarcusTextDecoratorViewManager.h +4 -0
  42. package/apple/MarcusTextDecoratorViewManager.mm +11 -0
  43. package/apple/MarcusTextInputDecoratorComponentView.mm +1 -1
  44. package/apple/MarcusTextInputDecoratorShadowNode.mm +1 -1
  45. package/apple/MarcusTextLayoutManager.h +40 -0
  46. package/apple/MarcusTextLayoutManager.mm +173 -0
  47. package/apple/MarkdownAttributes.h +23 -2
  48. package/apple/MarkdownAttributes.m +6 -0
  49. package/apple/MarkdownCopy.swift +63 -0
  50. package/apple/MarkdownDecorator.swift +2 -2
  51. package/apple/MarkdownFormatter+React.swift +23 -0
  52. package/apple/MarkdownFormatter.swift +869 -148
  53. package/apple/MarkdownParagraphLayoutManager.swift +455 -0
  54. package/apple/MarkdownSwiftInterop.h +0 -1
  55. package/apple/MarkdownTextBackground.swift +59 -1
  56. package/apple/MarkdownTextFieldObserver.swift +2 -2
  57. package/apple/MarkdownTextLayoutFragment.swift +159 -33
  58. package/apple/MarkdownTextLayoutManagerDelegate.swift +67 -3
  59. package/apple/MarkdownTextStorageDelegate.swift +2 -2
  60. package/apple/MarkdownTextViewObserver.swift +2 -2
  61. package/apple/MarkdownUtils.swift +38 -0
  62. package/apple/MarkdownWritingDirection.swift +132 -0
  63. package/apple/RCTMarcusStyle+Codegen.h +115 -9
  64. package/apple/RCTMarcusStyle.h +128 -31
  65. package/apple/RCTMarcusStyle.m +48 -0
  66. package/apple/RCTParagraphComponentView+Marcus.h +17 -0
  67. package/apple/RCTParagraphComponentView+Marcus.mm +38 -0
  68. package/apple/RCTTextInputComponentView+Marcus.mm +1 -25
  69. package/cpp/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorState.h +37 -0
  70. package/cpp/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorViewComponentDescriptor.h +17 -0
  71. package/lib/commonjs/MarcusTextDecoratorViewNativeComponent.ts +199 -0
  72. package/lib/commonjs/MarcusTextInputDecoratorViewNativeComponent.ts +145 -58
  73. package/lib/commonjs/MarkdownText.js +330 -0
  74. package/lib/commonjs/MarkdownText.js.map +1 -0
  75. package/lib/commonjs/MarkdownText.web.js +529 -0
  76. package/lib/commonjs/MarkdownText.web.js.map +1 -0
  77. package/lib/commonjs/MarkdownTextInput.js +22 -27
  78. package/lib/commonjs/MarkdownTextInput.js.map +1 -1
  79. package/lib/commonjs/MarkdownTextInput.web.js +12 -7
  80. package/lib/commonjs/MarkdownTextInput.web.js.map +1 -1
  81. package/lib/commonjs/atomUtils.js +112 -0
  82. package/lib/commonjs/atomUtils.js.map +1 -0
  83. package/lib/commonjs/childrenUtils.js +36 -0
  84. package/lib/commonjs/childrenUtils.js.map +1 -0
  85. package/lib/commonjs/index.js +8 -7
  86. package/lib/commonjs/index.js.map +1 -1
  87. package/lib/commonjs/math/environment.js +69 -0
  88. package/lib/commonjs/math/environment.js.map +1 -0
  89. package/lib/commonjs/math/index.js +61 -0
  90. package/lib/commonjs/math/index.js.map +1 -0
  91. package/lib/commonjs/math/mathjax.js +548 -0
  92. package/lib/commonjs/math/mathjax.js.map +1 -0
  93. package/lib/commonjs/metro.js +172 -0
  94. package/lib/commonjs/metro.js.map +1 -0
  95. package/lib/commonjs/parser/index.d.ts +3 -0
  96. package/lib/commonjs/parser/index.js +450 -42
  97. package/lib/commonjs/parser/index.js.map +4 -4
  98. package/lib/commonjs/stripUtils.js +743 -0
  99. package/lib/commonjs/stripUtils.js.map +1 -0
  100. package/lib/commonjs/styleUtils.js +194 -23
  101. package/lib/commonjs/styleUtils.js.map +1 -1
  102. package/lib/commonjs/web/MarkdownElements.css +67 -0
  103. package/lib/commonjs/web/MarkdownTextInput.css +9 -7
  104. package/lib/commonjs/web/utils/blockLayout.js +95 -7
  105. package/lib/commonjs/web/utils/blockLayout.js.map +1 -1
  106. package/lib/commonjs/web/utils/blockUtils.js +327 -54
  107. package/lib/commonjs/web/utils/blockUtils.js.map +1 -1
  108. package/lib/commonjs/web/utils/elementUtils.js +80 -0
  109. package/lib/commonjs/web/utils/elementUtils.js.map +1 -0
  110. package/lib/commonjs/web/utils/parserUtils.js +84 -28
  111. package/lib/commonjs/web/utils/parserUtils.js.map +1 -1
  112. package/lib/commonjs/web/utils/treeUtils.js +6 -0
  113. package/lib/commonjs/web/utils/treeUtils.js.map +1 -1
  114. package/lib/commonjs/web/utils/webStyleUtils.js +25 -5
  115. package/lib/commonjs/web/utils/webStyleUtils.js.map +1 -1
  116. package/lib/module/MarcusTextDecoratorViewNativeComponent.ts +199 -0
  117. package/lib/module/MarcusTextInputDecoratorViewNativeComponent.ts +145 -58
  118. package/lib/module/MarkdownText.js +325 -0
  119. package/lib/module/MarkdownText.js.map +1 -0
  120. package/lib/module/MarkdownText.web.js +524 -0
  121. package/lib/module/MarkdownText.web.js.map +1 -0
  122. package/lib/module/MarkdownTextInput.js +24 -29
  123. package/lib/module/MarkdownTextInput.js.map +1 -1
  124. package/lib/module/MarkdownTextInput.web.js +12 -7
  125. package/lib/module/MarkdownTextInput.web.js.map +1 -1
  126. package/lib/module/atomUtils.js +106 -0
  127. package/lib/module/atomUtils.js.map +1 -0
  128. package/lib/module/childrenUtils.js +33 -0
  129. package/lib/module/childrenUtils.js.map +1 -0
  130. package/lib/module/index.js +1 -1
  131. package/lib/module/index.js.map +1 -1
  132. package/lib/module/math/environment.js +67 -0
  133. package/lib/module/math/environment.js.map +1 -0
  134. package/lib/module/math/index.js +56 -0
  135. package/lib/module/math/index.js.map +1 -0
  136. package/lib/module/math/mathjax.js +541 -0
  137. package/lib/module/math/mathjax.js.map +1 -0
  138. package/lib/module/metro.js +169 -0
  139. package/lib/module/metro.js.map +1 -0
  140. package/lib/module/parser/index.d.ts +3 -0
  141. package/lib/module/parser/index.js +450 -42
  142. package/lib/module/parser/index.js.map +4 -4
  143. package/lib/module/stripUtils.js +739 -0
  144. package/lib/module/stripUtils.js.map +1 -0
  145. package/lib/module/styleUtils.js +196 -25
  146. package/lib/module/styleUtils.js.map +1 -1
  147. package/lib/module/web/MarkdownElements.css +67 -0
  148. package/lib/module/web/MarkdownTextInput.css +9 -7
  149. package/lib/module/web/utils/blockLayout.js +94 -8
  150. package/lib/module/web/utils/blockLayout.js.map +1 -1
  151. package/lib/module/web/utils/blockUtils.js +328 -56
  152. package/lib/module/web/utils/blockUtils.js.map +1 -1
  153. package/lib/module/web/utils/elementUtils.js +75 -0
  154. package/lib/module/web/utils/elementUtils.js.map +1 -0
  155. package/lib/module/web/utils/parserUtils.js +86 -30
  156. package/lib/module/web/utils/parserUtils.js.map +1 -1
  157. package/lib/module/web/utils/treeUtils.js +6 -0
  158. package/lib/module/web/utils/treeUtils.js.map +1 -1
  159. package/lib/module/web/utils/webStyleUtils.js +25 -5
  160. package/lib/module/web/utils/webStyleUtils.js.map +1 -1
  161. package/lib/typescript/commonjs/e2e/links.test.d.ts +2 -0
  162. package/lib/typescript/commonjs/e2e/links.test.d.ts.map +1 -0
  163. package/lib/typescript/commonjs/src/MarcusTextDecoratorViewNativeComponent.d.ts +93 -0
  164. package/lib/typescript/commonjs/src/MarcusTextDecoratorViewNativeComponent.d.ts.map +1 -0
  165. package/lib/typescript/commonjs/src/MarcusTextInputDecoratorViewNativeComponent.d.ts +64 -51
  166. package/lib/typescript/commonjs/src/MarcusTextInputDecoratorViewNativeComponent.d.ts.map +1 -1
  167. package/lib/typescript/commonjs/src/MarkdownText.d.ts +127 -0
  168. package/lib/typescript/commonjs/src/MarkdownText.d.ts.map +1 -0
  169. package/lib/typescript/commonjs/src/MarkdownText.web.d.ts +126 -0
  170. package/lib/typescript/commonjs/src/MarkdownText.web.d.ts.map +1 -0
  171. package/lib/typescript/commonjs/src/MarkdownTextInput.d.ts +23 -1
  172. package/lib/typescript/commonjs/src/MarkdownTextInput.d.ts.map +1 -1
  173. package/lib/typescript/commonjs/src/MarkdownTextInput.web.d.ts +26 -4
  174. package/lib/typescript/commonjs/src/MarkdownTextInput.web.d.ts.map +1 -1
  175. package/lib/typescript/commonjs/src/atomUtils.d.ts +58 -0
  176. package/lib/typescript/commonjs/src/atomUtils.d.ts.map +1 -0
  177. package/lib/typescript/commonjs/src/childrenUtils.d.ts +12 -0
  178. package/lib/typescript/commonjs/src/childrenUtils.d.ts.map +1 -0
  179. package/lib/typescript/commonjs/src/commonTypes.d.ts +39 -3
  180. package/lib/typescript/commonjs/src/commonTypes.d.ts.map +1 -1
  181. package/lib/typescript/commonjs/src/index.d.ts +2 -1
  182. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  183. package/lib/typescript/commonjs/src/math/environment.d.ts +7 -0
  184. package/lib/typescript/commonjs/src/math/environment.d.ts.map +1 -0
  185. package/lib/typescript/commonjs/src/math/index.d.ts +64 -0
  186. package/lib/typescript/commonjs/src/math/index.d.ts.map +1 -0
  187. package/lib/typescript/commonjs/src/math/mathjax.d.ts +63 -0
  188. package/lib/typescript/commonjs/src/math/mathjax.d.ts.map +1 -0
  189. package/lib/typescript/commonjs/src/metro.d.ts +59 -0
  190. package/lib/typescript/commonjs/src/metro.d.ts.map +1 -0
  191. package/lib/typescript/commonjs/src/parser/index.d.ts +3 -3
  192. package/lib/typescript/commonjs/src/parser/index.d.ts.map +1 -1
  193. package/lib/typescript/commonjs/src/parser/micromark-extension-mentions.d.ts.map +1 -1
  194. package/lib/typescript/commonjs/src/rangeUtils.d.ts +1 -1
  195. package/lib/typescript/commonjs/src/stripUtils.d.ts +108 -0
  196. package/lib/typescript/commonjs/src/stripUtils.d.ts.map +1 -0
  197. package/lib/typescript/commonjs/src/styleUtils.d.ts +62 -4
  198. package/lib/typescript/commonjs/src/styleUtils.d.ts.map +1 -1
  199. package/lib/typescript/commonjs/src/web/utils/blockLayout.d.ts +32 -4
  200. package/lib/typescript/commonjs/src/web/utils/blockLayout.d.ts.map +1 -1
  201. package/lib/typescript/commonjs/src/web/utils/blockUtils.d.ts +20 -8
  202. package/lib/typescript/commonjs/src/web/utils/blockUtils.d.ts.map +1 -1
  203. package/lib/typescript/commonjs/src/web/utils/elementUtils.d.ts +37 -0
  204. package/lib/typescript/commonjs/src/web/utils/elementUtils.d.ts.map +1 -0
  205. package/lib/typescript/commonjs/src/web/utils/parserUtils.d.ts +5 -5
  206. package/lib/typescript/commonjs/src/web/utils/parserUtils.d.ts.map +1 -1
  207. package/lib/typescript/commonjs/src/web/utils/treeUtils.d.ts +6 -1
  208. package/lib/typescript/commonjs/src/web/utils/treeUtils.d.ts.map +1 -1
  209. package/lib/typescript/commonjs/src/web/utils/webStyleUtils.d.ts +2 -2
  210. package/lib/typescript/commonjs/src/web/utils/webStyleUtils.d.ts.map +1 -1
  211. package/lib/typescript/module/e2e/links.test.d.ts +2 -0
  212. package/lib/typescript/module/e2e/links.test.d.ts.map +1 -0
  213. package/lib/typescript/module/src/MarcusTextDecoratorViewNativeComponent.d.ts +93 -0
  214. package/lib/typescript/module/src/MarcusTextDecoratorViewNativeComponent.d.ts.map +1 -0
  215. package/lib/typescript/module/src/MarcusTextInputDecoratorViewNativeComponent.d.ts +64 -51
  216. package/lib/typescript/module/src/MarcusTextInputDecoratorViewNativeComponent.d.ts.map +1 -1
  217. package/lib/typescript/module/src/MarkdownText.d.ts +127 -0
  218. package/lib/typescript/module/src/MarkdownText.d.ts.map +1 -0
  219. package/lib/typescript/module/src/MarkdownText.web.d.ts +126 -0
  220. package/lib/typescript/module/src/MarkdownText.web.d.ts.map +1 -0
  221. package/lib/typescript/module/src/MarkdownTextInput.d.ts +23 -1
  222. package/lib/typescript/module/src/MarkdownTextInput.d.ts.map +1 -1
  223. package/lib/typescript/module/src/MarkdownTextInput.web.d.ts +26 -4
  224. package/lib/typescript/module/src/MarkdownTextInput.web.d.ts.map +1 -1
  225. package/lib/typescript/module/src/atomUtils.d.ts +58 -0
  226. package/lib/typescript/module/src/atomUtils.d.ts.map +1 -0
  227. package/lib/typescript/module/src/childrenUtils.d.ts +12 -0
  228. package/lib/typescript/module/src/childrenUtils.d.ts.map +1 -0
  229. package/lib/typescript/module/src/commonTypes.d.ts +39 -3
  230. package/lib/typescript/module/src/commonTypes.d.ts.map +1 -1
  231. package/lib/typescript/module/src/index.d.ts +2 -1
  232. package/lib/typescript/module/src/index.d.ts.map +1 -1
  233. package/lib/typescript/module/src/math/environment.d.ts +7 -0
  234. package/lib/typescript/module/src/math/environment.d.ts.map +1 -0
  235. package/lib/typescript/module/src/math/index.d.ts +64 -0
  236. package/lib/typescript/module/src/math/index.d.ts.map +1 -0
  237. package/lib/typescript/module/src/math/mathjax.d.ts +63 -0
  238. package/lib/typescript/module/src/math/mathjax.d.ts.map +1 -0
  239. package/lib/typescript/module/src/metro.d.ts +59 -0
  240. package/lib/typescript/module/src/metro.d.ts.map +1 -0
  241. package/lib/typescript/module/src/parser/index.d.ts +3 -3
  242. package/lib/typescript/module/src/parser/index.d.ts.map +1 -1
  243. package/lib/typescript/module/src/parser/micromark-extension-mentions.d.ts.map +1 -1
  244. package/lib/typescript/module/src/rangeUtils.d.ts +1 -1
  245. package/lib/typescript/module/src/stripUtils.d.ts +108 -0
  246. package/lib/typescript/module/src/stripUtils.d.ts.map +1 -0
  247. package/lib/typescript/module/src/styleUtils.d.ts +62 -4
  248. package/lib/typescript/module/src/styleUtils.d.ts.map +1 -1
  249. package/lib/typescript/module/src/web/utils/blockLayout.d.ts +32 -4
  250. package/lib/typescript/module/src/web/utils/blockLayout.d.ts.map +1 -1
  251. package/lib/typescript/module/src/web/utils/blockUtils.d.ts +20 -8
  252. package/lib/typescript/module/src/web/utils/blockUtils.d.ts.map +1 -1
  253. package/lib/typescript/module/src/web/utils/elementUtils.d.ts +37 -0
  254. package/lib/typescript/module/src/web/utils/elementUtils.d.ts.map +1 -0
  255. package/lib/typescript/module/src/web/utils/parserUtils.d.ts +5 -5
  256. package/lib/typescript/module/src/web/utils/parserUtils.d.ts.map +1 -1
  257. package/lib/typescript/module/src/web/utils/treeUtils.d.ts +6 -1
  258. package/lib/typescript/module/src/web/utils/treeUtils.d.ts.map +1 -1
  259. package/lib/typescript/module/src/web/utils/webStyleUtils.d.ts +2 -2
  260. package/lib/typescript/module/src/web/utils/webStyleUtils.d.ts.map +1 -1
  261. package/package.json +29 -2
  262. package/react-native.config.js +8 -1
  263. package/src/MarcusTextDecoratorViewNativeComponent.ts +199 -0
  264. package/src/MarcusTextInputDecoratorViewNativeComponent.ts +145 -58
  265. package/src/MarkdownText.tsx +472 -0
  266. package/src/MarkdownText.web.tsx +668 -0
  267. package/src/MarkdownTextInput.tsx +42 -38
  268. package/src/MarkdownTextInput.web.tsx +36 -13
  269. package/src/atomUtils.ts +112 -0
  270. package/src/childrenUtils.ts +39 -0
  271. package/src/commonTypes.ts +69 -9
  272. package/src/index.tsx +2 -1
  273. package/src/math/environment.ts +68 -0
  274. package/src/math/index.ts +78 -0
  275. package/src/math/mathjax.ts +604 -0
  276. package/src/metro.ts +238 -0
  277. package/src/parser/index.ts +175 -10
  278. package/src/parser/micromark-extension-mentions.ts +104 -11
  279. package/src/stripUtils.ts +919 -0
  280. package/src/styleUtils.ts +290 -26
  281. package/src/web/MarkdownElements.css +67 -0
  282. package/src/web/MarkdownTextInput.css +9 -7
  283. package/src/web/utils/blockLayout.ts +124 -14
  284. package/src/web/utils/blockUtils.ts +365 -59
  285. package/src/web/utils/elementUtils.ts +87 -0
  286. package/src/web/utils/parserUtils.ts +119 -31
  287. package/src/web/utils/treeUtils.ts +6 -1
  288. package/src/web/utils/webStyleUtils.ts +32 -8
  289. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBackgroundColorSpan.kt +0 -6
  290. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBoldSpan.kt +0 -6
  291. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownItalicSpan.kt +0 -6
  292. package/apple/RCTMarcusStyle+Codegen.mm +0 -64
  293. package/apple/RCTMarcusUtils.h +0 -26
  294. package/apple/RCTMarcusUtils.mm +0 -26
@@ -1,8 +1,8 @@
1
1
  import UIKit
2
2
 
3
3
  /// Draws the parts of markdown rendering that plain attributed-string attributes
4
- /// can't express: blockquote ribbons down the left edge, and rounded background
5
- /// pills behind mentions.
4
+ /// can't express: the box behind a code block, blockquote ribbons down the edge
5
+ /// the text begins at, and rounded background pills behind mentions.
6
6
  ///
7
7
  /// Only constructed by `MarkdownTextLayoutManagerDelegate`, so it stays internal
8
8
  /// to the Swift half of the module.
@@ -10,17 +10,36 @@ final class MarkdownTextLayoutFragment: NSTextLayoutFragment {
10
10
 
11
11
  var markdownStyle: RCTMarcusStyle?
12
12
  var depth: Int = 0
13
- /// Distance from the paragraph's left edge to the blockquote's own box:
13
+ /// Distance from the edge the line begins at to the blockquote's own box:
14
14
  /// whatever containers nesting outside it indent by, plus the room left for a
15
15
  /// list marker drawn in front of the ribbons.
16
16
  var outerIndent: CGFloat = 0
17
+ /// Which edge that is. Read from the string rather than resolved here: UIKit
18
+ /// answers the same question while laying the line out and keeps the answer,
19
+ /// so `MarkdownFormatter` works one out and leaves it under
20
+ /// `.marcusRightToLeft` for both drawing paths to share.
21
+ var rightToLeft = false
17
22
  var mentions: [MarkdownTextBackgroundWithRange] = []
23
+ /// The box this line belongs to, if it is part of a code block, and which of
24
+ /// the block's ends it is: a fragment is one line of the block, so the
25
+ /// corners are only rounded on the two that terminate it.
26
+ var codeBlock: MarkdownCodeBlock?
27
+ var opensCodeBlock = false
28
+ var closesCodeBlock = false
18
29
 
19
30
  // MARK: - NSTextLayoutFragment
20
31
 
21
32
  override var renderingSurfaceBounds: CGRect {
22
- guard depth > 0 else { return super.renderingSurfaceBounds }
23
- return boundingRect.union(super.renderingSurfaceBounds)
33
+ var bounds = super.renderingSurfaceBounds
34
+
35
+ if depth > 0 {
36
+ bounds = boundingRect.union(bounds)
37
+ }
38
+ if let codeBlockRect {
39
+ bounds = codeBlockRect.union(bounds)
40
+ }
41
+
42
+ return bounds
24
43
  }
25
44
 
26
45
  override func draw(at point: CGPoint, in context: CGContext) {
@@ -29,34 +48,123 @@ final class MarkdownTextLayoutFragment: NSTextLayoutFragment {
29
48
  return
30
49
  }
31
50
 
51
+ // Before the ribbons: a quoted block's box would otherwise cover the bar
52
+ // drawn beside it.
53
+ drawCodeBlock()
32
54
  drawBlockquoteRibbons()
33
55
  drawMentions()
34
56
 
35
57
  super.draw(at: point, in: context)
36
58
  }
37
59
 
60
+ // MARK: - Code block box
61
+
62
+ /// This line's slice of the box behind the block it is part of.
63
+ ///
64
+ /// The fragment's own frame rather than the text in it: the frame spans the
65
+ /// width the line was laid out in and carries the paragraph spacing that
66
+ /// holds the box's padding open, which is exactly what the box covers. The
67
+ /// paragraph is drawn the same way, one rect over all its lines, in
68
+ /// `MarkdownParagraphLayoutManager`.
69
+ private var codeBlockRect: CGRect? {
70
+ guard let block = codeBlock else { return nil }
71
+
72
+ let frame = layoutFragmentFrame
73
+ // The frame is in the container's coordinates and everything drawn here is
74
+ // in the fragment's own, so the container's edges arrive shifted by where
75
+ // the fragment sits in it.
76
+ let width = textLayoutManager?.textContainer?.size.width ?? frame.width
77
+ // The insets are measured from the edge the block's lines begin at, so in a
78
+ // right-to-left block they swap sides: the padded one is the right, and the
79
+ // bare margin is all that is left on the far side.
80
+ let near = block.rightToLeft ? block.marginHorizontal : block.leadingInset
81
+ let far = block.rightToLeft ? block.leadingInset : block.marginHorizontal
82
+ let left = near - frame.origin.x
83
+ let right = width - frame.origin.x - far
84
+ let top = opensCodeBlock ? block.marginVertical : 0
85
+ let bottom = frame.height - (closesCodeBlock ? block.marginVertical : 0)
86
+
87
+ guard right > left, bottom > top else { return nil }
88
+
89
+ return CGRect(x: left, y: top, width: right - left, height: bottom - top)
90
+ }
91
+
92
+ private func drawCodeBlock() {
93
+ guard let block = codeBlock, let rect = codeBlockRect else { return }
94
+
95
+ var corners: UIRectCorner = []
96
+ if opensCodeBlock {
97
+ corners.insert([.topLeft, .topRight])
98
+ }
99
+ if closesCodeBlock {
100
+ corners.insert([.bottomLeft, .bottomRight])
101
+ }
102
+
103
+ let radius = block.borderRadius
104
+ let path =
105
+ corners.isEmpty || radius <= 0
106
+ ? UIBezierPath(rect: rect)
107
+ : UIBezierPath(
108
+ roundedRect: rect,
109
+ byRoundingCorners: corners,
110
+ cornerRadii: CGSize(width: radius, height: radius)
111
+ )
112
+
113
+ block.color.setFill()
114
+ path.fill()
115
+
116
+ stroke(path, color: block.borderColor, width: block.borderWidth)
117
+ }
118
+
119
+ /// Draws a box's border on the inside of the box.
120
+ ///
121
+ /// Clipped to the path and stroked at twice the width, so the outer half of
122
+ /// the stroke is trimmed away: a bordered box then covers exactly the room an
123
+ /// unbordered one does, which is what a border on an element does on the web
124
+ /// and what `MarkdownParagraphLayoutManager` draws for the same style.
125
+ private func stroke(_ path: UIBezierPath, color: UIColor?, width: CGFloat) {
126
+ guard let color, width > 0, let context = UIGraphicsGetCurrentContext() else {
127
+ return
128
+ }
129
+
130
+ context.saveGState()
131
+ path.addClip()
132
+ color.setStroke()
133
+ path.lineWidth = width * 2
134
+ path.stroke()
135
+ context.restoreGState()
136
+ }
137
+
38
138
  // MARK: - Custom elements
39
139
 
40
140
  private func drawBlockquoteRibbons() {
41
141
  guard depth > 0, let style = markdownStyle else { return }
42
142
 
43
- let borderWidth = style.blockquoteBorderWidth
44
- let shift =
45
- style.blockquoteMarginLeft + borderWidth + style.blockquotePaddingLeft
143
+ let borderWidth = style.blockquote.borderWidth
144
+ let shift = style.blockquoteShift
145
+ let radius = style.blockquote.borderRadius
46
146
 
47
- style.blockquoteBorderColor.setFill()
147
+ (style.blockquote.borderColor ?? .clear).setFill()
48
148
 
149
+ // `boundingRect` is the whole strip the bars fill, so the innermost one --
150
+ // the level nearest the text -- is at whichever of its sides the line
151
+ // begins at, and the rest step away from there.
49
152
  let bounds = boundingRect
50
153
  for level in 0..<depth {
51
- let x = bounds.origin.x + CGFloat(level) * shift
52
- UIRectFill(
53
- CGRect(
54
- x: x,
55
- y: bounds.origin.y,
56
- width: borderWidth,
57
- height: bounds.size.height
58
- )
154
+ let bar = CGRect(
155
+ x: rightToLeft
156
+ ? bounds.maxX - CGFloat(level) * shift - borderWidth
157
+ : bounds.minX + CGFloat(level) * shift,
158
+ y: bounds.origin.y,
159
+ width: borderWidth,
160
+ height: bounds.size.height
59
161
  )
162
+
163
+ if radius > 0 {
164
+ UIBezierPath(roundedRect: bar, cornerRadius: radius).fill()
165
+ } else {
166
+ UIRectFill(bar)
167
+ }
60
168
  }
61
169
  }
62
170
 
@@ -124,22 +232,31 @@ final class MarkdownTextLayoutFragment: NSTextLayoutFragment {
124
232
  ) as? UIFont
125
233
  else { continue }
126
234
 
235
+ // Where the run's own ends fall on this line, which is what the kerning
236
+ // reserved space against and what the corners are rounded on.
237
+ let opens = intersection.location == mention.range.location
238
+ let closes = NSMaxRange(intersection) == NSMaxRange(mention.range)
239
+ let background = mention.textBackground
240
+ let left = startLocation.x - (opens ? background.paddingHorizontal : 0)
241
+ let right = endLocation.x - (closes ? background.marginHorizontal : 0)
242
+
127
243
  let backgroundRect = CGRect(
128
- x: lineBounds.origin.x + startLocation.x,
129
- y: lineBounds.origin.y + startLocation.y - font.ascender,
130
- width: endLocation.x - startLocation.x,
131
- height: font.lineHeight
244
+ x: lineBounds.origin.x + left,
245
+ y: lineBounds.origin.y + startLocation.y - font.ascender
246
+ - background.paddingVertical,
247
+ width: right - left,
248
+ height: font.lineHeight + 2 * background.paddingVertical
132
249
  )
133
250
 
134
251
  var corners: UIRectCorner = []
135
- if intersection.location == mention.range.location {
252
+ if opens {
136
253
  corners.insert([.topLeft, .bottomLeft])
137
254
  }
138
- if NSMaxRange(intersection) == NSMaxRange(mention.range) {
255
+ if closes {
139
256
  corners.insert([.topRight, .bottomRight])
140
257
  }
141
258
 
142
- let radius = mention.textBackground.borderRadius
259
+ let radius = background.borderRadius
143
260
  let path =
144
261
  corners.isEmpty
145
262
  ? UIBezierPath(rect: backgroundRect)
@@ -149,15 +266,18 @@ final class MarkdownTextLayoutFragment: NSTextLayoutFragment {
149
266
  cornerRadii: CGSize(width: radius, height: radius)
150
267
  )
151
268
 
152
- mention.textBackground.color.setFill()
269
+ background.color.setFill()
153
270
  path.fill()
271
+
272
+ stroke(path, color: background.borderColor, width: background.borderWidth)
154
273
  }
155
274
  }
156
275
  }
157
276
 
158
277
  // MARK: - Helpers
159
278
 
160
- /// The strip to the left of the text that the blockquote ribbons occupy.
279
+ /// The strip beside the text that the blockquote ribbons occupy, on whichever
280
+ /// side the line begins at.
161
281
  private var boundingRect: CGRect {
162
282
  var bounds = CGRect.null
163
283
  for lineFragment in textLineFragments
@@ -172,21 +292,27 @@ final class MarkdownTextLayoutFragment: NSTextLayoutFragment {
172
292
  // infinite rendering surface rather than an empty one.
173
293
  guard !bounds.isNull, let style = markdownStyle else { return bounds }
174
294
 
175
- let borderWidth = style.blockquoteBorderWidth
176
- let shift =
177
- style.blockquoteMarginLeft + borderWidth + style.blockquotePaddingLeft
295
+ let borderWidth = style.blockquote.borderWidth
296
+ let shift = style.blockquoteShift
178
297
 
179
- // The ribbons sit at a fixed offset from the paragraph's own left edge,
180
- // which is the text origin less however far this line is indented. Walking
298
+ // The ribbons sit at a fixed offset from the edge the paragraph begins at,
299
+ // which is the end of the text that the indent holds off that edge. Walking
181
300
  // back from the text by the quote's own indent instead would move the bar
182
301
  // whenever the line's indent changes -- a list nested inside the quote adds
183
302
  // some, a marker drawn in front of the ribbon takes some away -- and split
184
303
  // the bar between the lines of one quote.
304
+ //
305
+ // A right-to-left line is indented from the right, so it is the far side of
306
+ // the text that the indent is added back to, and the strip then runs
307
+ // leftwards from there rather than rightwards.
185
308
  let indent =
186
309
  firstLineParagraphStyle?.firstLineHeadIndent ?? shift * CGFloat(depth)
310
+ let width = borderWidth + shift * CGFloat(depth - 1)
311
+ let offset = indent - (outerIndent + style.blockquote.marginStart)
187
312
 
188
- bounds.origin.x -= indent - (outerIndent + style.blockquoteMarginLeft)
189
- bounds.size.width = borderWidth + shift * CGFloat(depth - 1)
313
+ bounds.origin.x =
314
+ rightToLeft ? bounds.maxX + offset - width : bounds.origin.x - offset
315
+ bounds.size.width = width
190
316
  return bounds
191
317
  }
192
318
 
@@ -8,9 +8,9 @@ public final class MarkdownTextLayoutManagerDelegate: NSObject,
8
8
  {
9
9
 
10
10
  private let textStorage: NSTextStorage
11
- private let markdownUtils: RCTMarcusUtils
11
+ private let markdownUtils: MarkdownUtils
12
12
 
13
- @objc public init(textStorage: NSTextStorage, markdownUtils: RCTMarcusUtils)
13
+ @objc public init(textStorage: NSTextStorage, markdownUtils: MarkdownUtils)
14
14
  {
15
15
  self.textStorage = textStorage
16
16
  self.markdownUtils = markdownUtils
@@ -69,7 +69,15 @@ public final class MarkdownTextLayoutManagerDelegate: NSObject,
69
69
  )
70
70
  }
71
71
 
72
- guard depth != nil || !mentions.isEmpty else { return plain() }
72
+ let paragraphRange = NSRange(
73
+ location: index,
74
+ length: attributedString.length
75
+ )
76
+ let codeBlock = codeBlock(in: paragraphRange)
77
+
78
+ guard depth != nil || !mentions.isEmpty || codeBlock != nil else {
79
+ return plain()
80
+ }
73
81
 
74
82
  let fragment = MarkdownTextLayoutFragment(
75
83
  textElement: textElement,
@@ -78,7 +86,63 @@ public final class MarkdownTextLayoutManagerDelegate: NSObject,
78
86
  fragment.markdownStyle = markdownUtils.markdownStyle
79
87
  fragment.depth = depth?.intValue ?? 0
80
88
  fragment.outerIndent = CGFloat(outerIndent?.doubleValue ?? 0)
89
+ fragment.rightToLeft =
90
+ textStorage.attribute(
91
+ .marcusRightToLeft,
92
+ at: index,
93
+ effectiveRange: nil
94
+ ) != nil
81
95
  fragment.mentions = mentions
96
+ fragment.codeBlock = codeBlock?.block
97
+ fragment.opensCodeBlock = codeBlock.map {
98
+ $0.range.location >= paragraphRange.location
99
+ } ?? false
100
+ fragment.closesCodeBlock = codeBlock.map {
101
+ NSMaxRange($0.range) <= NSMaxRange(paragraphRange)
102
+ } ?? false
82
103
  return fragment
83
104
  }
105
+
106
+ /// The code block this paragraph is a line of, and how far that block reaches.
107
+ ///
108
+ /// Searched across the paragraph rather than read at its first character: a
109
+ /// block quoted with `>` begins after the marker that opens the line, so the
110
+ /// paragraph starts outside the block it belongs to.
111
+ private func codeBlock(
112
+ in paragraphRange: NSRange
113
+ ) -> (block: MarkdownCodeBlock, range: NSRange)? {
114
+ let clamped = NSIntersectionRange(
115
+ paragraphRange,
116
+ NSRange(location: 0, length: textStorage.length)
117
+ )
118
+ guard clamped.length > 0 else { return nil }
119
+
120
+ var found: MarkdownCodeBlock?
121
+ var location = NSNotFound
122
+
123
+ textStorage.enumerateAttribute(
124
+ .marcusCodeBlock,
125
+ in: clamped,
126
+ options: []
127
+ ) { value, range, stop in
128
+ guard let block = value as? MarkdownCodeBlock else { return }
129
+ found = block
130
+ location = range.location
131
+ stop.pointee = true
132
+ }
133
+
134
+ guard let block = found else { return nil }
135
+
136
+ // The run above is only this paragraph's share of the block. Which end of
137
+ // the block the paragraph is at needs the whole of it.
138
+ var blockRange = NSRange()
139
+ _ = textStorage.attribute(
140
+ .marcusCodeBlock,
141
+ at: location,
142
+ longestEffectiveRange: &blockRange,
143
+ in: NSRange(location: 0, length: textStorage.length)
144
+ )
145
+
146
+ return (block, blockRange)
147
+ }
84
148
  }
@@ -10,9 +10,9 @@ public final class MarkdownTextStorageDelegate: NSObject, NSTextStorageDelegate
10
10
  // let the text view deallocate while this object is still installed as its
11
11
  // storage delegate and while KVO observers are registered on it.
12
12
  private let textView: RCTUITextView
13
- private let markdownUtils: RCTMarcusUtils
13
+ private let markdownUtils: MarkdownUtils
14
14
 
15
- @objc public init(textView: RCTUITextView, markdownUtils: RCTMarcusUtils) {
15
+ @objc public init(textView: RCTUITextView, markdownUtils: MarkdownUtils) {
16
16
  self.textView = textView
17
17
  self.markdownUtils = markdownUtils
18
18
  super.init()
@@ -7,9 +7,9 @@ import UIKit
7
7
 
8
8
  // Strong on purpose -- see MarkdownTextStorageDelegate.
9
9
  private let textView: RCTUITextView
10
- private let markdownUtils: RCTMarcusUtils
10
+ private let markdownUtils: MarkdownUtils
11
11
 
12
- @objc public init(textView: RCTUITextView, markdownUtils: RCTMarcusUtils) {
12
+ @objc public init(textView: RCTUITextView, markdownUtils: MarkdownUtils) {
13
13
  self.textView = textView
14
14
  self.markdownUtils = markdownUtils
15
15
  super.init()
@@ -0,0 +1,38 @@
1
+ import Foundation
2
+
3
+ /// Parses and formats in one step for the view layer.
4
+ ///
5
+ /// Parsing is stateful -- the shared parser owns a memo cache -- so this holds
6
+ /// the style and parser id that a text input's props supply, and hands both to
7
+ /// the stateless formatter on each pass. One instance per text input, only ever
8
+ /// touched on the main thread.
9
+ ///
10
+ /// The `Text` side needs none of this: its ranges are parsed in JavaScript and
11
+ /// arrive as props, so `MarcusTextLayoutManager` calls the formatter directly.
12
+ @objc public final class MarkdownUtils: NSObject {
13
+
14
+ /// Both are assigned after init -- props arrive after the view hierarchy is
15
+ /// built -- and formatting bails out while either is still nil.
16
+ @objc public var markdownStyle: RCTMarcusStyle?
17
+ @objc public var parserId: NSNumber?
18
+
19
+ @objc public func applyMarkdownFormatting(
20
+ _ attributedString: NSMutableAttributedString,
21
+ withDefaultTextAttributes defaultTextAttributes: [NSAttributedString.Key:
22
+ Any]
23
+ ) {
24
+ guard let markdownStyle, let parserId else { return }
25
+
26
+ let ranges = MarkdownParser.sharedParser.parse(
27
+ attributedString.string,
28
+ withParserId: parserId
29
+ )
30
+
31
+ MarkdownFormatter.format(
32
+ attributedString,
33
+ defaultTextAttributes: defaultTextAttributes,
34
+ ranges: ranges,
35
+ style: markdownStyle
36
+ )
37
+ }
38
+ }
@@ -0,0 +1,132 @@
1
+ import Foundation
2
+
3
+ /// Which way a line reads, worked out the way the Unicode Bidirectional
4
+ /// Algorithm's rules P2 and P3 work it out: the first character with a strong
5
+ /// direction decides, characters inside an isolate are not that character, and
6
+ /// a line with no strong character at all reads left to right.
7
+ ///
8
+ /// Worked out rather than read off the paragraph style, because there is
9
+ /// nothing there to read. `NSWritingDirectionNatural` is what a paragraph
10
+ /// carries by default and what React Native leaves it as -- `RCTTextAttributes`
11
+ /// only writes the direction through when it is *not* natural -- and UIKit
12
+ /// resolves it while laying the line out without writing the answer back. The
13
+ /// layout that follows is already right, since a head indent opens its gutter
14
+ /// on whichever edge the line begins at. It is only the shapes drawn beside the
15
+ /// text by hand -- a quote's ribbons, a code block's box -- that have no way to
16
+ /// ask, and this is what they ask instead.
17
+ ///
18
+ /// A deliberate approximation of the real bidi classes, and one worth naming:
19
+ /// classes R and AL are taken to be the letters of the right-to-left scripts,
20
+ /// class L to be every other letter, and neither to be anything else. What that
21
+ /// gets wrong is a line opening with a right-to-left *number* and continuing in
22
+ /// Latin -- `١٢٣ abc`, which the algorithm calls left to right and so does this,
23
+ /// but by skipping the digits as unclassified rather than by classifying them.
24
+ /// The cases it gets right are every line that begins in a script, which is
25
+ /// every line anyone writes. Being wrong here costs a bar on the far side of a
26
+ /// quote; it cannot affect where the text itself goes, which stays UIKit's to
27
+ /// decide, and that is the reason the guess is allowed to be a guess.
28
+ enum MarkdownWritingDirection {
29
+
30
+ /// Whether the text in `range` reads right to left.
31
+ static func isRightToLeft(_ text: NSString, in range: NSRange) -> Bool {
32
+ var index = max(range.location, 0)
33
+ let end = min(NSMaxRange(range), text.length)
34
+ /// Unmatched isolate initiators still open. P2 skips everything between one
35
+ /// and its matching PDI, so an embedded run cannot decide the line holding
36
+ /// it: `\u{2066}LTR\u{2069} عربي` reads right to left despite opening with
37
+ /// three Latin letters.
38
+ var isolated = 0
39
+
40
+ while index < end {
41
+ let unit = text.character(at: index)
42
+ index += 1
43
+
44
+ var value = UInt32(unit)
45
+
46
+ // A surrogate pair is one character, and the planes past the first are
47
+ // where the historic right-to-left scripts live.
48
+ if UTF16.isLeadSurrogate(unit), index < end {
49
+ let trail = text.character(at: index)
50
+ if UTF16.isTrailSurrogate(trail) {
51
+ value =
52
+ (value - 0xD800) * 0x400 + (UInt32(trail) - 0xDC00) + 0x10000
53
+ index += 1
54
+ }
55
+ }
56
+
57
+ guard let scalar = UnicodeScalar(value) else { continue }
58
+
59
+ if isolateInitiators.contains(scalar) {
60
+ isolated += 1
61
+ continue
62
+ }
63
+ if scalar == popDirectionalIsolate {
64
+ isolated = max(isolated - 1, 0)
65
+ continue
66
+ }
67
+ if isolated > 0 {
68
+ continue
69
+ }
70
+
71
+ if rightToLeft.contains(scalar) {
72
+ return true
73
+ }
74
+ if strong.contains(scalar) {
75
+ return false
76
+ }
77
+ }
78
+
79
+ return false
80
+ }
81
+
82
+ // MARK: - Classes
83
+
84
+ /// Scalars of bidi class R or AL: the letters of the right-to-left scripts,
85
+ /// and the two zero-width marks that stand in for one.
86
+ ///
87
+ /// The blocks are taken whole and then intersected with the letters, which is
88
+ /// what keeps the Arabic-Indic digits and the Hebrew points out of it -- they
89
+ /// sit in the same blocks as the letters but decide nothing.
90
+ private static let rightToLeft: CharacterSet = {
91
+ var set = CharacterSet()
92
+
93
+ for block in [
94
+ 0x0590...0x08FF, // Hebrew through Arabic Extended-A
95
+ 0xFB1D...0xFDFF, // Hebrew and Arabic presentation forms A
96
+ 0xFE70...0xFEFF, // Arabic presentation forms B
97
+ 0x10800...0x10FFF, // Cypriot through Hanifi Rohingya
98
+ 0x1E800...0x1EFFF, // Mende Kikakui through Arabic mathematical
99
+ ] {
100
+ guard
101
+ let first = UnicodeScalar(UInt32(block.lowerBound)),
102
+ let last = UnicodeScalar(UInt32(block.upperBound))
103
+ else { continue }
104
+ set.insert(charactersIn: first...last)
105
+ }
106
+
107
+ return set.intersection(letters).union(
108
+ // ARABIC LETTER MARK, RIGHT-TO-LEFT MARK.
109
+ CharacterSet(charactersIn: "\u{061C}\u{200F}")
110
+ )
111
+ }()
112
+
113
+ /// Everything that decides a direction at all: the letters, plus the marks.
114
+ /// Whether a scalar in here decides on right or left is then `rightToLeft`.
115
+ private static let strong: CharacterSet = letters.union(
116
+ // ARABIC LETTER MARK, LEFT-TO-RIGHT MARK, RIGHT-TO-LEFT MARK.
117
+ CharacterSet(charactersIn: "\u{061C}\u{200E}\u{200F}")
118
+ )
119
+
120
+ /// Letters as the algorithm means them, which is the L\* categories alone.
121
+ /// Foundation's own set folds in the M\* ones, and a combining mark carries no
122
+ /// direction: a line opening with one is decided by whatever it is on.
123
+ private static let letters = CharacterSet.letters.subtracting(.nonBaseCharacters)
124
+
125
+ /// LEFT-TO-RIGHT, RIGHT-TO-LEFT and FIRST STRONG ISOLATE.
126
+ private static let isolateInitiators = CharacterSet(
127
+ charactersIn: "\u{2066}\u{2067}\u{2068}"
128
+ )
129
+
130
+ /// POP DIRECTIONAL ISOLATE.
131
+ private static let popDirectionalIsolate: UnicodeScalar = "\u{2069}"
132
+ }