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
@@ -0,0 +1,455 @@
1
+ import UIKit
2
+
3
+ /// Draws the parts of markdown rendering that attributed-string attributes
4
+ /// cannot express -- the box behind a code block, blockquote ribbons, rounded
5
+ /// mention pills and a list's bullets -- for a `Text` rather than a `TextInput`.
6
+ ///
7
+ /// The input draws these in TextKit 2, from `MarkdownTextLayoutFragment`, which
8
+ /// is only reachable through a text view's own `NSTextLayoutManager`. A
9
+ /// paragraph is laid out and drawn by `RCTTextLayoutManager`, which is TextKit
10
+ /// 1, so the same shapes are drawn here instead -- during the background pass,
11
+ /// which is exactly where something belongs that has to sit under the glyphs and
12
+ /// over nothing.
13
+ ///
14
+ /// The geometry is the same in both, and deliberately expressed the same way:
15
+ /// a code block's box spans the lines it covers, a ribbon sits at a fixed
16
+ /// offset from the edge the line begins at, and a pill is a font-height box
17
+ /// around the run's baseline. The bullets are the one shape with no counterpart
18
+ /// in the input, which shows the marker that was typed rather than drawing one.
19
+ @objc public final class MarkdownParagraphLayoutManager: NSLayoutManager {
20
+
21
+ @objc public var markdownStyle: RCTMarcusStyle?
22
+
23
+ public override func drawBackground(
24
+ forGlyphRange glyphsToShow: NSRange,
25
+ at origin: CGPoint
26
+ ) {
27
+ super.drawBackground(forGlyphRange: glyphsToShow, at: origin)
28
+
29
+ guard markdownStyle != nil, let textStorage else { return }
30
+
31
+ let characterRange = self.characterRange(
32
+ forGlyphRange: glyphsToShow,
33
+ actualGlyphRange: nil
34
+ )
35
+
36
+ // Before the ribbons: a quoted block's box would otherwise cover the bar
37
+ // drawn beside it.
38
+ drawCodeBlocks(in: textStorage, characterRange, at: origin)
39
+ drawBlockquoteRibbons(in: textStorage, glyphsToShow, at: origin)
40
+ drawMentions(in: textStorage, characterRange, at: origin)
41
+ drawListBullets(in: textStorage, characterRange, at: origin)
42
+ }
43
+
44
+ // MARK: - Code blocks
45
+
46
+ /// One box per fenced or indented block, drawn behind the whole of it.
47
+ ///
48
+ /// The whole block at once rather than a rect per line: the fills of two
49
+ /// abutting lines each antialias their shared edge, and the pair of
50
+ /// half-covered rows reads as a seam across the box. It is the same reason
51
+ /// the ribbons below are drawn per run of lines rather than per line.
52
+ private func drawCodeBlocks(
53
+ in textStorage: NSTextStorage,
54
+ _ characterRange: NSRange,
55
+ at origin: CGPoint
56
+ ) {
57
+ textStorage.enumerateAttribute(
58
+ .marcusCodeBlock,
59
+ in: characterRange,
60
+ options: []
61
+ ) { value, range, _ in
62
+ guard let block = value as? MarkdownCodeBlock else { return }
63
+
64
+ // The visible range can start mid-block while scrolling, and a box drawn
65
+ // to that would round its corners in the middle of the code. Ask the
66
+ // storage for the block's whole extent instead.
67
+ var blockRange = range
68
+ _ = textStorage.attribute(
69
+ .marcusCodeBlock,
70
+ at: range.location,
71
+ longestEffectiveRange: &blockRange,
72
+ in: NSRange(location: 0, length: textStorage.length)
73
+ )
74
+
75
+ drawCodeBlock(block, blockRange, at: origin)
76
+ }
77
+ }
78
+
79
+ private func drawCodeBlock(
80
+ _ block: MarkdownCodeBlock,
81
+ _ range: NSRange,
82
+ at origin: CGPoint
83
+ ) {
84
+ let blockGlyphRange = glyphRange(
85
+ forCharacterRange: range,
86
+ actualCharacterRange: nil
87
+ )
88
+
89
+ // Line fragment rects rather than the text inside them: a fragment spans
90
+ // the width the line was laid out in and carries the paragraph spacing that
91
+ // holds the box's own padding open, which is exactly the box.
92
+ var bounds = CGRect.null
93
+ var glyphIndex = blockGlyphRange.location
94
+
95
+ while glyphIndex < NSMaxRange(blockGlyphRange) {
96
+ var lineGlyphRange = NSRange()
97
+ let lineRect = lineFragmentRect(
98
+ forGlyphAt: glyphIndex,
99
+ effectiveRange: &lineGlyphRange
100
+ )
101
+
102
+ // An empty effective range would spin here rather than crash, which is
103
+ // worse; bail instead.
104
+ guard lineGlyphRange.length > 0 else { break }
105
+
106
+ bounds = bounds.isNull ? lineRect : bounds.union(lineRect)
107
+ glyphIndex = NSMaxRange(lineGlyphRange)
108
+ }
109
+
110
+ guard !bounds.isNull else { return }
111
+
112
+ // The insets are measured from the edge the block's lines begin at, so in a
113
+ // right-to-left block they swap sides: the padded one is the right, and the
114
+ // bare margin is all that is left on the far side.
115
+ let near = block.rightToLeft ? block.marginHorizontal : block.leadingInset
116
+ let far = block.rightToLeft ? block.leadingInset : block.marginHorizontal
117
+
118
+ let rect = CGRect(
119
+ x: origin.x + bounds.minX + near,
120
+ y: origin.y + bounds.minY + block.marginVertical,
121
+ width: bounds.width - near - far,
122
+ height: bounds.height - 2 * block.marginVertical
123
+ )
124
+
125
+ guard rect.width > 0, rect.height > 0 else { return }
126
+
127
+ let path = UIBezierPath(roundedRect: rect, cornerRadius: block.borderRadius)
128
+ block.color.setFill()
129
+ path.fill()
130
+
131
+ stroke(path, color: block.borderColor, width: block.borderWidth)
132
+ }
133
+
134
+ /// Draws a box's border on the inside of the box.
135
+ ///
136
+ /// Clipped to the path and stroked at twice the width, so the outer half of
137
+ /// the stroke is trimmed away: a bordered box then covers exactly the room an
138
+ /// unbordered one does, which is what a border on an element does on the web
139
+ /// and what the Android spans draw.
140
+ private func stroke(_ path: UIBezierPath, color: UIColor?, width: CGFloat) {
141
+ guard let color, width > 0, let context = UIGraphicsGetCurrentContext() else {
142
+ return
143
+ }
144
+
145
+ context.saveGState()
146
+ path.addClip()
147
+ color.setStroke()
148
+ path.lineWidth = width * 2
149
+ path.stroke()
150
+ context.restoreGState()
151
+ }
152
+
153
+ // MARK: - Blockquote ribbons
154
+
155
+ /// One ribbon per run of consecutive lines at the same nesting.
156
+ ///
157
+ /// Walked a line fragment at a time rather than per attribute run, because
158
+ /// the line that opens a quote steps over a marker and the ones continuing it
159
+ /// do not, so consecutive lines share the attribute but not their indent.
160
+ /// Lines that do agree are then filled as a single rect: two abutting fills
161
+ /// at a fractional boundary each antialias their shared edge, and the pair of
162
+ /// half-covered rows reads as a seam across the bar.
163
+ private func drawBlockquoteRibbons(
164
+ in textStorage: NSTextStorage,
165
+ _ glyphsToShow: NSRange,
166
+ at origin: CGPoint
167
+ ) {
168
+ guard let style = markdownStyle else { return }
169
+
170
+ var pending: (depth: Int, indent: CGFloat, rightToLeft: Bool, rect: CGRect)?
171
+ var didSetFill = false
172
+ var glyphIndex = glyphsToShow.location
173
+
174
+ func flush() {
175
+ guard let run = pending else { return }
176
+ pending = nil
177
+
178
+ if !didSetFill {
179
+ (style.blockquote.borderColor ?? .clear).setFill()
180
+ didSetFill = true
181
+ }
182
+
183
+ // Measured from the edge the line begins at, never from the text: a list
184
+ // nested inside the quote adds indent and a marker drawn in front of the
185
+ // ribbon takes some away, and either would split the bar. The line
186
+ // fragment spans the width the line was laid out in, so its two sides are
187
+ // the two candidate edges and the line's own direction picks one.
188
+ //
189
+ // Everything past that point is then a signed distance, the way Android
190
+ // writes the same arithmetic in `MarkdownBlockIndentSpan`: `step` carries
191
+ // the direction, and each bar is normalised into a rect at the end.
192
+ let step: CGFloat = run.rightToLeft ? -1 : 1
193
+ let edge = run.rightToLeft ? run.rect.maxX : run.rect.minX
194
+ let first =
195
+ origin.x + edge + step * (run.indent + style.blockquote.marginStart)
196
+ let radius = style.blockquote.borderRadius
197
+
198
+ for level in 0..<run.depth {
199
+ let near = first + step * CGFloat(level) * style.blockquoteShift
200
+ let far = near + step * style.blockquote.borderWidth
201
+ let bar = CGRect(
202
+ x: min(near, far),
203
+ y: origin.y + run.rect.minY,
204
+ width: style.blockquote.borderWidth,
205
+ height: run.rect.height
206
+ )
207
+
208
+ if radius > 0 {
209
+ UIBezierPath(roundedRect: bar, cornerRadius: radius).fill()
210
+ } else {
211
+ UIRectFill(bar)
212
+ }
213
+ }
214
+ }
215
+
216
+ while glyphIndex < NSMaxRange(glyphsToShow) {
217
+ var lineGlyphRange = NSRange()
218
+ let lineRect = lineFragmentRect(
219
+ forGlyphAt: glyphIndex,
220
+ effectiveRange: &lineGlyphRange
221
+ )
222
+
223
+ // An empty effective range would spin here rather than crash, which is
224
+ // worse; bail instead.
225
+ guard lineGlyphRange.length > 0 else { break }
226
+
227
+ let line = characterRange(
228
+ forGlyphRange: lineGlyphRange,
229
+ actualGlyphRange: nil
230
+ )
231
+
232
+ if line.location < textStorage.length,
233
+ let depth = textStorage.attribute(
234
+ .marcusBlockquoteDepth,
235
+ at: line.location,
236
+ effectiveRange: nil
237
+ ) as? NSNumber,
238
+ depth.intValue > 0
239
+ {
240
+ let indent = CGFloat(
241
+ (textStorage.attribute(
242
+ .marcusBlockquoteIndent,
243
+ at: line.location,
244
+ effectiveRange: nil
245
+ ) as? NSNumber)?.doubleValue ?? 0
246
+ )
247
+
248
+ let rightToLeft =
249
+ textStorage.attribute(
250
+ .marcusRightToLeft,
251
+ at: line.location,
252
+ effectiveRange: nil
253
+ ) != nil
254
+
255
+ // A line that turns round starts a new run even at the same depth and
256
+ // indent: its bars hang from the other side, so there is no single rect
257
+ // the two could share.
258
+ if let run = pending, run.depth == depth.intValue, run.indent == indent,
259
+ run.rightToLeft == rightToLeft
260
+ {
261
+ pending?.rect = run.rect.union(lineRect)
262
+ } else {
263
+ flush()
264
+ pending = (depth.intValue, indent, rightToLeft, lineRect)
265
+ }
266
+ } else {
267
+ flush()
268
+ }
269
+
270
+ glyphIndex = NSMaxRange(lineGlyphRange)
271
+ }
272
+
273
+ flush()
274
+ }
275
+
276
+ // MARK: - Mention pills
277
+
278
+ private func drawMentions(
279
+ in textStorage: NSTextStorage,
280
+ _ characterRange: NSRange,
281
+ at origin: CGPoint
282
+ ) {
283
+ guard let textContainer = textContainers.first else { return }
284
+
285
+ textStorage.enumerateAttribute(
286
+ .marcusTextBackground,
287
+ in: characterRange,
288
+ options: []
289
+ ) { value, mentionRange, _ in
290
+ guard let background = value as? MarkdownTextBackground else { return }
291
+
292
+ drawMention(
293
+ background,
294
+ in: textStorage,
295
+ mentionRange,
296
+ textContainer: textContainer,
297
+ at: origin
298
+ )
299
+ }
300
+ }
301
+
302
+ /// Fills one mention, a line fragment at a time.
303
+ ///
304
+ /// Soft wrapping can split a mention, so each piece is filled on its own and
305
+ /// only the sides that actually terminate the mention are rounded -- the
306
+ /// break itself stays square, so the two halves read as one run.
307
+ private func drawMention(
308
+ _ background: MarkdownTextBackground,
309
+ in textStorage: NSTextStorage,
310
+ _ mentionRange: NSRange,
311
+ textContainer: NSTextContainer,
312
+ at origin: CGPoint
313
+ ) {
314
+ let mentionGlyphRange = glyphRange(
315
+ forCharacterRange: mentionRange,
316
+ actualCharacterRange: nil
317
+ )
318
+ let radius = background.borderRadius
319
+ background.color.setFill()
320
+
321
+ var glyphIndex = mentionGlyphRange.location
322
+
323
+ while glyphIndex < NSMaxRange(mentionGlyphRange) {
324
+ var lineGlyphRange = NSRange()
325
+ let lineRect = lineFragmentRect(
326
+ forGlyphAt: glyphIndex,
327
+ effectiveRange: &lineGlyphRange
328
+ )
329
+
330
+ guard lineGlyphRange.length > 0 else { return }
331
+
332
+ let segment = NSIntersectionRange(lineGlyphRange, mentionGlyphRange)
333
+
334
+ guard segment.length > 0 else {
335
+ glyphIndex = NSMaxRange(lineGlyphRange)
336
+ continue
337
+ }
338
+
339
+ let box = boundingRect(forGlyphRange: segment, in: textContainer)
340
+ let segmentCharacters = characterRange(
341
+ forGlyphRange: segment,
342
+ actualGlyphRange: nil
343
+ )
344
+
345
+ // The pill is a font-height box around the baseline, not the line
346
+ // fragment: an explicit `lineHeight` makes the fragment taller than the
347
+ // text, and a pill drawn to that height would swallow the gap between
348
+ // two lines.
349
+ if box.width > 0,
350
+ segmentCharacters.location < textStorage.length,
351
+ let font = textStorage.attribute(
352
+ .font,
353
+ at: segmentCharacters.location,
354
+ effectiveRange: nil
355
+ ) as? UIFont
356
+ {
357
+ // Where the run's own ends fall on this line, which is what the kerning
358
+ // reserved space against and what the corners are rounded on.
359
+ let opens = segment.location == mentionGlyphRange.location
360
+ let closes = NSMaxRange(segment) == NSMaxRange(mentionGlyphRange)
361
+ let baseline = lineRect.minY + location(forGlyphAt: segment.location).y
362
+ let left = box.minX - (opens ? background.paddingHorizontal : 0)
363
+ let right = box.maxX - (closes ? background.marginHorizontal : 0)
364
+ let rect = CGRect(
365
+ x: origin.x + left,
366
+ y: origin.y + baseline - font.ascender - background.paddingVertical,
367
+ width: right - left,
368
+ height: font.lineHeight + 2 * background.paddingVertical
369
+ )
370
+
371
+ var corners: UIRectCorner = []
372
+ if opens {
373
+ corners.insert([.topLeft, .bottomLeft])
374
+ }
375
+ if closes {
376
+ corners.insert([.topRight, .bottomRight])
377
+ }
378
+
379
+ let path =
380
+ corners.isEmpty || radius <= 0
381
+ ? UIBezierPath(rect: rect)
382
+ : UIBezierPath(
383
+ roundedRect: rect,
384
+ byRoundingCorners: corners,
385
+ cornerRadii: CGSize(width: radius, height: radius)
386
+ )
387
+ if rect.width > 0 {
388
+ path.fill()
389
+ stroke(path, color: background.borderColor, width: background.borderWidth)
390
+ }
391
+ }
392
+
393
+ glyphIndex = NSMaxRange(lineGlyphRange)
394
+ }
395
+ }
396
+ // MARK: - List bullets
397
+
398
+ /// One circle per unordered list item, in the advance its marker character
399
+ /// was left holding.
400
+ ///
401
+ /// `MarkdownFormatter` kerned that character to exactly the circle's width and
402
+ /// left its glyph undrawn, so the shape is the marker: it starts where the
403
+ /// glyph run starts and needs no measuring of its own. Only the height is a
404
+ /// question, and the answer is the line's -- a bullet marks the line rather
405
+ /// than sitting on its baseline, so it is centred on the line's own box the
406
+ /// way it is on every other platform.
407
+ private func drawListBullets(
408
+ in textStorage: NSTextStorage,
409
+ _ characterRange: NSRange,
410
+ at origin: CGPoint
411
+ ) {
412
+ guard let textContainer = textContainers.first else { return }
413
+
414
+ textStorage.enumerateAttribute(
415
+ .marcusListBullet,
416
+ in: characterRange,
417
+ options: []
418
+ ) { value, markerRange, _ in
419
+ guard let bullet = value as? MarkdownListBullet, bullet.diameter > 0 else {
420
+ return
421
+ }
422
+
423
+ let markerGlyphRange = glyphRange(
424
+ forCharacterRange: markerRange,
425
+ actualCharacterRange: nil
426
+ )
427
+
428
+ guard markerGlyphRange.length > 0 else { return }
429
+
430
+ let box = boundingRect(forGlyphRange: markerGlyphRange, in: textContainer)
431
+ let line = lineFragmentUsedRect(
432
+ forGlyphAt: markerGlyphRange.location,
433
+ effectiveRange: nil
434
+ )
435
+
436
+ let rect = CGRect(
437
+ x: origin.x + box.minX,
438
+ y: origin.y + line.midY - bullet.diameter / 2,
439
+ width: bullet.diameter,
440
+ height: bullet.diameter
441
+ )
442
+
443
+ bullet.color.setFill()
444
+ UIBezierPath(ovalIn: rect).fill()
445
+ }
446
+ }
447
+ }
448
+
449
+ extension RCTMarcusStyle {
450
+ /// Width one level of blockquote nesting takes up: its own margin, the ribbon
451
+ /// itself, and the padding between the ribbon and the text.
452
+ var blockquoteShift: CGFloat {
453
+ blockquote.marginStart + blockquote.borderWidth + blockquote.paddingStart
454
+ }
455
+ }
@@ -14,7 +14,6 @@
14
14
 
15
15
  #import <RNMarcus/MarcusRange.h>
16
16
  #import <RNMarcus/RCTMarcusStyle.h>
17
- #import <RNMarcus/RCTMarcusUtils.h>
18
17
 
19
18
  #if __has_include(<RNMarcus/RNMarcus-Swift.h>)
20
19
  #import <RNMarcus/RNMarcus-Swift.h>
@@ -1,12 +1,27 @@
1
1
  import UIKit
2
2
 
3
- /// The rounded background drawn behind a mention.
3
+ /// The rounded box drawn behind a run of text: a mention's pill, or an inline
4
+ /// run of code.
4
5
  ///
5
6
  /// Stored as an attributed-string attribute value under
6
7
  /// `.marcusTextBackground`, so it stays an object rather than a struct.
8
+ ///
9
+ /// The padding is the room between the glyphs and the box, the margin the room
10
+ /// between the box and the text either side of it. Horizontally both are held
11
+ /// open by the kerning `MarkdownFormatter` puts on the characters the run starts
12
+ /// and ends with, so all that is left when drawing is to place the edges inside
13
+ /// the space they reserved. Vertically only the padding is used, and the box
14
+ /// grows out of the font's own box into the line's spacing: a line cannot be
15
+ /// pushed apart for one run on it, so there is nothing a vertical margin could
16
+ /// hold open.
7
17
  final class MarkdownTextBackground: NSObject {
8
18
  var color: UIColor = .clear
19
+ var borderColor: UIColor?
20
+ var borderWidth: CGFloat = 0
9
21
  var borderRadius: CGFloat = 0
22
+ var paddingHorizontal: CGFloat = 0
23
+ var paddingVertical: CGFloat = 0
24
+ var marginHorizontal: CGFloat = 0
10
25
  }
11
26
 
12
27
  /// A `MarkdownTextBackground` paired with the range it applies to. Only ever a
@@ -15,3 +30,46 @@ struct MarkdownTextBackgroundWithRange {
15
30
  let textBackground: MarkdownTextBackground
16
31
  let range: NSRange
17
32
  }
33
+
34
+ /// The box drawn behind a fenced or indented code block.
35
+ ///
36
+ /// Stored under `.marcusCodeBlock` over the block's own text, and read by both
37
+ /// drawing paths -- `MarkdownTextLayoutFragment` in an input,
38
+ /// `MarkdownParagraphLayoutManager` in a paragraph -- so that the two draw the
39
+ /// same box from the same numbers.
40
+ ///
41
+ /// `leadingInset` is where the box's near edge sits, measured from the edge the
42
+ /// block's lines begin at: the indent they already carried, plus the horizontal
43
+ /// margin. It is worked out while formatting, where the indents are, rather
44
+ /// than rediscovered from a paragraph style at every frame.
45
+ ///
46
+ /// Which edge that is comes from `rightToLeft`, taken from the block's first
47
+ /// line. One answer for the whole box rather than one per line, because the box
48
+ /// is one rect over all of them -- the same reason there is a single inset for
49
+ /// lines that could each have been indented differently.
50
+ final class MarkdownCodeBlock: NSObject {
51
+ var color: UIColor = .clear
52
+ var borderColor: UIColor?
53
+ var borderWidth: CGFloat = 0
54
+ var borderRadius: CGFloat = 0
55
+ var marginHorizontal: CGFloat = 0
56
+ var marginVertical: CGFloat = 0
57
+ var leadingInset: CGFloat = 0
58
+ var rightToLeft = false
59
+ }
60
+
61
+ /// The bullet drawn in place of an unordered item's marker.
62
+ ///
63
+ /// Stored under `.marcusListBullet` over the marker character itself, whose own
64
+ /// glyph is left undrawn: the character stays in the string -- it is what a
65
+ /// reader copies and what VoiceOver announces -- and the circle is drawn in the
66
+ /// advance it would have taken.
67
+ ///
68
+ /// `diameter` comes from the base font size rather than from a length of its
69
+ /// own, so a list marks itself in proportion to the prose it marks. The marker's
70
+ /// advance is exactly this wide, which is what lets the circle be drawn from the
71
+ /// left edge of the glyph run and nothing else.
72
+ final class MarkdownListBullet: NSObject {
73
+ var color: UIColor = .clear
74
+ var diameter: CGFloat = 0
75
+ }
@@ -8,12 +8,12 @@ import UIKit
8
8
 
9
9
  // Strong on purpose -- see MarkdownTextStorageDelegate.
10
10
  private let textField: RCTUITextField
11
- private let markdownUtils: RCTMarcusUtils
11
+ private let markdownUtils: MarkdownUtils
12
12
 
13
13
  /// Guards against the KVO observer re-entering while we assign `attributedText`.
14
14
  private var active = true
15
15
 
16
- @objc public init(textField: RCTUITextField, markdownUtils: RCTMarcusUtils)
16
+ @objc public init(textField: RCTUITextField, markdownUtils: MarkdownUtils)
17
17
  {
18
18
  self.textField = textField
19
19
  self.markdownUtils = markdownUtils