react-native-marcus 0.1.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 (304) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +227 -0
  3. package/RNMarcus.podspec +78 -0
  4. package/android/build.gradle +220 -0
  5. package/android/proguard-rules.pro +1 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/AndroidManifestNew.xml +2 -0
  8. package/android/src/main/cpp/CMakeLists.txt +41 -0
  9. package/android/src/main/cpp/MarkdownParser.cpp +49 -0
  10. package/android/src/main/cpp/MarkdownParser.h +56 -0
  11. package/android/src/main/cpp/OnLoad.cpp +17 -0
  12. package/android/src/main/java/app/getbullet/marcus/MarcusModule.kt +28 -0
  13. package/android/src/main/java/app/getbullet/marcus/MarcusPackage.kt +31 -0
  14. package/android/src/main/java/app/getbullet/marcus/MarcusTextInputDecoratorView.kt +63 -0
  15. package/android/src/main/java/app/getbullet/marcus/MarcusTextInputDecoratorViewManager.kt +32 -0
  16. package/android/src/main/java/app/getbullet/marcus/MarcusTextInputDecoratorViewManagerSpec.kt +21 -0
  17. package/android/src/main/java/app/getbullet/marcus/MarkdownFormatter.kt +333 -0
  18. package/android/src/main/java/app/getbullet/marcus/MarkdownParseResult.kt +12 -0
  19. package/android/src/main/java/app/getbullet/marcus/MarkdownParser.kt +89 -0
  20. package/android/src/main/java/app/getbullet/marcus/MarkdownRange.kt +11 -0
  21. package/android/src/main/java/app/getbullet/marcus/MarkdownStyle.kt +81 -0
  22. package/android/src/main/java/app/getbullet/marcus/MarkdownTextWatcher.kt +17 -0
  23. package/android/src/main/java/app/getbullet/marcus/MarkdownUtils.kt +84 -0
  24. package/android/src/main/java/app/getbullet/marcus/ReactLineHeight.java +29 -0
  25. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBackgroundColorSpan.kt +6 -0
  26. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBackgroundSpan.kt +105 -0
  27. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBlockIndentSpan.kt +67 -0
  28. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBoldSpan.kt +6 -0
  29. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownFontFamilySpan.kt +28 -0
  30. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownFontSizeSpan.kt +7 -0
  31. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownForegroundColorSpan.kt +6 -0
  32. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownGapSpan.kt +57 -0
  33. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownItalicSpan.kt +6 -0
  34. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownLineHeightSpan.kt +21 -0
  35. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownSpan.kt +7 -0
  36. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownStrikethroughSpan.kt +5 -0
  37. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownUnderlineSpan.kt +5 -0
  38. package/android/src/main/new_arch/CMakeLists.txt +70 -0
  39. package/android/src/main/new_arch/RNMarcusSpec.cpp +43 -0
  40. package/android/src/main/new_arch/RNMarcusSpec.h +36 -0
  41. package/android/src/main/new_arch/react/renderer/components/RNMarcusSpec/MarcusTextInputDecoratorShadowNode.cpp +218 -0
  42. package/android/src/main/new_arch/react/renderer/components/RNMarcusSpec/MarcusTextInputDecoratorShadowNode.h +54 -0
  43. package/android/src/reactNativeVersionPatch/CustomMountingManager/81/app/getbullet/marcus/CustomFabricUIManager.java +156 -0
  44. package/android/src/reactNativeVersionPatch/CustomMountingManager/latest/app/getbullet/marcus/CustomFabricUIManager.java +130 -0
  45. package/android/src/test/java/app/getbullet/marcus/MarkdownFormatterTest.kt +205 -0
  46. package/android/src/test/java/app/getbullet/marcus/RenderModel.kt +78 -0
  47. package/android/src/test/resources/render-model.txt +583 -0
  48. package/apple/MarcusModule.h +8 -0
  49. package/apple/MarcusModule.mm +26 -0
  50. package/apple/MarcusRange.h +15 -0
  51. package/apple/MarcusRange.mm +15 -0
  52. package/apple/MarcusTextInputDecoratorComponentView.h +12 -0
  53. package/apple/MarcusTextInputDecoratorComponentView.mm +121 -0
  54. package/apple/MarcusTextInputDecoratorShadowNode.cpp +251 -0
  55. package/apple/MarcusTextInputDecoratorShadowNode.h +65 -0
  56. package/apple/MarcusTextInputDecoratorShadowNode.mm +204 -0
  57. package/apple/MarcusTextInputDecoratorViewManager.h +8 -0
  58. package/apple/MarcusTextInputDecoratorViewManager.mm +11 -0
  59. package/apple/MarkdownAdaptiveImageGlyph.swift +44 -0
  60. package/apple/MarkdownAttributes.h +31 -0
  61. package/apple/MarkdownAttributes.m +9 -0
  62. package/apple/MarkdownBackedTextInputDelegate.swift +117 -0
  63. package/apple/MarkdownDecorator.swift +316 -0
  64. package/apple/MarkdownFonts.swift +21 -0
  65. package/apple/MarkdownFormatter+React.swift +51 -0
  66. package/apple/MarkdownFormatter.swift +509 -0
  67. package/apple/MarkdownLog.h +10 -0
  68. package/apple/MarkdownLog.mm +6 -0
  69. package/apple/MarkdownParser.swift +200 -0
  70. package/apple/MarkdownSwiftInterop.h +23 -0
  71. package/apple/MarkdownTextBackground.swift +17 -0
  72. package/apple/MarkdownTextFieldObserver.swift +90 -0
  73. package/apple/MarkdownTextLayoutFragment.swift +205 -0
  74. package/apple/MarkdownTextLayoutManagerDelegate.swift +84 -0
  75. package/apple/MarkdownTextStorageDelegate.swift +32 -0
  76. package/apple/MarkdownTextViewObserver.swift +30 -0
  77. package/apple/RCTMarcusStyle+Codegen.h +18 -0
  78. package/apple/RCTMarcusStyle+Codegen.mm +64 -0
  79. package/apple/RCTMarcusStyle.h +41 -0
  80. package/apple/RCTMarcusStyle.m +5 -0
  81. package/apple/RCTMarcusUtils.h +26 -0
  82. package/apple/RCTMarcusUtils.mm +26 -0
  83. package/apple/RCTTextInput+AdaptiveImageGlyph.h +16 -0
  84. package/apple/RCTTextInput+AdaptiveImageGlyph.mm +79 -0
  85. package/apple/RCTTextInputComponentView+Marcus.h +11 -0
  86. package/apple/RCTTextInputComponentView+Marcus.mm +79 -0
  87. package/cpp/MarkdownCxx.modulemap +8 -0
  88. package/cpp/MarkdownGlobal.cpp +48 -0
  89. package/cpp/MarkdownGlobal.h +33 -0
  90. package/cpp/MarkdownWorkletParser.cpp +93 -0
  91. package/cpp/MarkdownWorkletParser.h +45 -0
  92. package/cpp/RuntimeDecorator.cpp +39 -0
  93. package/cpp/RuntimeDecorator.h +14 -0
  94. package/cpp/react/renderer/components/RNMarcusSpec/MarcusTextInputDecoratorState.h +33 -0
  95. package/cpp/react/renderer/components/RNMarcusSpec/MarcusTextInputDecoratorViewComponentDescriptor.h +17 -0
  96. package/lib/commonjs/MarcusTextInputDecoratorViewNativeComponent.ts +77 -0
  97. package/lib/commonjs/MarkdownTextInput.js +99 -0
  98. package/lib/commonjs/MarkdownTextInput.js.map +1 -0
  99. package/lib/commonjs/MarkdownTextInput.web.js +618 -0
  100. package/lib/commonjs/MarkdownTextInput.web.js.map +1 -0
  101. package/lib/commonjs/NativeMarcusModule.js +9 -0
  102. package/lib/commonjs/NativeMarcusModule.js.map +1 -0
  103. package/lib/commonjs/commonTypes.js +2 -0
  104. package/lib/commonjs/commonTypes.js.map +1 -0
  105. package/lib/commonjs/global.d.js +2 -0
  106. package/lib/commonjs/global.d.js.map +1 -0
  107. package/lib/commonjs/index.js +27 -0
  108. package/lib/commonjs/index.js.map +1 -0
  109. package/lib/commonjs/package.json +1 -0
  110. package/lib/commonjs/parser/index.js +7030 -0
  111. package/lib/commonjs/parser/index.js.map +7 -0
  112. package/lib/commonjs/rangeUtils.js +71 -0
  113. package/lib/commonjs/rangeUtils.js.map +1 -0
  114. package/lib/commonjs/react-native-web.d.js +2 -0
  115. package/lib/commonjs/react-native-web.d.js.map +1 -0
  116. package/lib/commonjs/styleUtils.js +91 -0
  117. package/lib/commonjs/styleUtils.js.map +1 -0
  118. package/lib/commonjs/web/InputHistory.js +111 -0
  119. package/lib/commonjs/web/InputHistory.js.map +1 -0
  120. package/lib/commonjs/web/MarkdownTextInput.css +72 -0
  121. package/lib/commonjs/web/utils/blockLayout.js +132 -0
  122. package/lib/commonjs/web/utils/blockLayout.js.map +1 -0
  123. package/lib/commonjs/web/utils/blockUtils.js +219 -0
  124. package/lib/commonjs/web/utils/blockUtils.js.map +1 -0
  125. package/lib/commonjs/web/utils/browserUtils.js +40 -0
  126. package/lib/commonjs/web/utils/browserUtils.js.map +1 -0
  127. package/lib/commonjs/web/utils/cursorUtils.js +157 -0
  128. package/lib/commonjs/web/utils/cursorUtils.js.map +1 -0
  129. package/lib/commonjs/web/utils/inputUtils.js +152 -0
  130. package/lib/commonjs/web/utils/inputUtils.js.map +1 -0
  131. package/lib/commonjs/web/utils/measureUtils.js +0 -0
  132. package/lib/commonjs/web/utils/measureUtils.js.map +1 -0
  133. package/lib/commonjs/web/utils/parserUtils.js +552 -0
  134. package/lib/commonjs/web/utils/parserUtils.js.map +1 -0
  135. package/lib/commonjs/web/utils/treeUtils.js +109 -0
  136. package/lib/commonjs/web/utils/treeUtils.js.map +1 -0
  137. package/lib/commonjs/web/utils/webStyleUtils.js +59 -0
  138. package/lib/commonjs/web/utils/webStyleUtils.js.map +1 -0
  139. package/lib/module/MarcusTextInputDecoratorViewNativeComponent.ts +77 -0
  140. package/lib/module/MarkdownTextInput.js +94 -0
  141. package/lib/module/MarkdownTextInput.js.map +1 -0
  142. package/lib/module/MarkdownTextInput.web.js +612 -0
  143. package/lib/module/MarkdownTextInput.web.js.map +1 -0
  144. package/lib/module/NativeMarcusModule.js +5 -0
  145. package/lib/module/NativeMarcusModule.js.map +1 -0
  146. package/lib/module/commonTypes.js +2 -0
  147. package/lib/module/commonTypes.js.map +1 -0
  148. package/lib/module/global.d.js +2 -0
  149. package/lib/module/global.d.js.map +1 -0
  150. package/lib/module/index.js +5 -0
  151. package/lib/module/index.js.map +1 -0
  152. package/lib/module/package.json +1 -0
  153. package/lib/module/parser/index.js +7029 -0
  154. package/lib/module/parser/index.js.map +7 -0
  155. package/lib/module/rangeUtils.js +66 -0
  156. package/lib/module/rangeUtils.js.map +1 -0
  157. package/lib/module/react-native-web.d.js +2 -0
  158. package/lib/module/react-native-web.d.js.map +1 -0
  159. package/lib/module/styleUtils.js +88 -0
  160. package/lib/module/styleUtils.js.map +1 -0
  161. package/lib/module/web/InputHistory.js +106 -0
  162. package/lib/module/web/InputHistory.js.map +1 -0
  163. package/lib/module/web/MarkdownTextInput.css +72 -0
  164. package/lib/module/web/utils/blockLayout.js +128 -0
  165. package/lib/module/web/utils/blockLayout.js.map +1 -0
  166. package/lib/module/web/utils/blockUtils.js +210 -0
  167. package/lib/module/web/utils/blockUtils.js.map +1 -0
  168. package/lib/module/web/utils/browserUtils.js +36 -0
  169. package/lib/module/web/utils/browserUtils.js.map +1 -0
  170. package/lib/module/web/utils/cursorUtils.js +150 -0
  171. package/lib/module/web/utils/cursorUtils.js.map +1 -0
  172. package/lib/module/web/utils/inputUtils.js +144 -0
  173. package/lib/module/web/utils/inputUtils.js.map +1 -0
  174. package/lib/module/web/utils/measureUtils.js +0 -0
  175. package/lib/module/web/utils/measureUtils.js.map +1 -0
  176. package/lib/module/web/utils/parserUtils.js +547 -0
  177. package/lib/module/web/utils/parserUtils.js.map +1 -0
  178. package/lib/module/web/utils/treeUtils.js +103 -0
  179. package/lib/module/web/utils/treeUtils.js.map +1 -0
  180. package/lib/module/web/utils/webStyleUtils.js +54 -0
  181. package/lib/module/web/utils/webStyleUtils.js.map +1 -0
  182. package/lib/typescript/commonjs/e2e/checks.test.d.ts +2 -0
  183. package/lib/typescript/commonjs/e2e/checks.test.d.ts.map +1 -0
  184. package/lib/typescript/commonjs/e2e/editing.test.d.ts +2 -0
  185. package/lib/typescript/commonjs/e2e/editing.test.d.ts.map +1 -0
  186. package/lib/typescript/commonjs/e2e/helpers.d.ts +23 -0
  187. package/lib/typescript/commonjs/e2e/helpers.d.ts.map +1 -0
  188. package/lib/typescript/commonjs/e2e/smoke.test.d.ts +2 -0
  189. package/lib/typescript/commonjs/e2e/smoke.test.d.ts.map +1 -0
  190. package/lib/typescript/commonjs/package.json +1 -0
  191. package/lib/typescript/commonjs/src/MarcusTextInputDecoratorViewNativeComponent.d.ts +63 -0
  192. package/lib/typescript/commonjs/src/MarcusTextInputDecoratorViewNativeComponent.d.ts.map +1 -0
  193. package/lib/typescript/commonjs/src/MarkdownTextInput.d.ts +22 -0
  194. package/lib/typescript/commonjs/src/MarkdownTextInput.d.ts.map +1 -0
  195. package/lib/typescript/commonjs/src/MarkdownTextInput.web.d.ts +41 -0
  196. package/lib/typescript/commonjs/src/MarkdownTextInput.web.d.ts.map +1 -0
  197. package/lib/typescript/commonjs/src/NativeMarcusModule.d.ts +7 -0
  198. package/lib/typescript/commonjs/src/NativeMarcusModule.d.ts.map +1 -0
  199. package/lib/typescript/commonjs/src/commonTypes.d.ts +10 -0
  200. package/lib/typescript/commonjs/src/commonTypes.d.ts.map +1 -0
  201. package/lib/typescript/commonjs/src/index.d.ts +5 -0
  202. package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
  203. package/lib/typescript/commonjs/src/parser/index.d.ts +8 -0
  204. package/lib/typescript/commonjs/src/parser/index.d.ts.map +1 -0
  205. package/lib/typescript/commonjs/src/parser/micromark-extension-mentions.d.ts +8 -0
  206. package/lib/typescript/commonjs/src/parser/micromark-extension-mentions.d.ts.map +1 -0
  207. package/lib/typescript/commonjs/src/rangeUtils.d.ts +6 -0
  208. package/lib/typescript/commonjs/src/rangeUtils.d.ts.map +1 -0
  209. package/lib/typescript/commonjs/src/styleUtils.d.ts +8 -0
  210. package/lib/typescript/commonjs/src/styleUtils.d.ts.map +1 -0
  211. package/lib/typescript/commonjs/src/web/InputHistory.d.ts +24 -0
  212. package/lib/typescript/commonjs/src/web/InputHistory.d.ts.map +1 -0
  213. package/lib/typescript/commonjs/src/web/utils/blockLayout.d.ts +41 -0
  214. package/lib/typescript/commonjs/src/web/utils/blockLayout.d.ts.map +1 -0
  215. package/lib/typescript/commonjs/src/web/utils/blockUtils.d.ts +33 -0
  216. package/lib/typescript/commonjs/src/web/utils/blockUtils.d.ts.map +1 -0
  217. package/lib/typescript/commonjs/src/web/utils/browserUtils.d.ts +7 -0
  218. package/lib/typescript/commonjs/src/web/utils/browserUtils.d.ts.map +1 -0
  219. package/lib/typescript/commonjs/src/web/utils/cursorUtils.d.ts +12 -0
  220. package/lib/typescript/commonjs/src/web/utils/cursorUtils.d.ts.map +1 -0
  221. package/lib/typescript/commonjs/src/web/utils/inputUtils.d.ts +12 -0
  222. package/lib/typescript/commonjs/src/web/utils/inputUtils.d.ts.map +1 -0
  223. package/lib/typescript/commonjs/src/web/utils/measureUtils.d.ts +22 -0
  224. package/lib/typescript/commonjs/src/web/utils/measureUtils.d.ts.map +1 -0
  225. package/lib/typescript/commonjs/src/web/utils/parserUtils.d.ts +44 -0
  226. package/lib/typescript/commonjs/src/web/utils/parserUtils.d.ts.map +1 -0
  227. package/lib/typescript/commonjs/src/web/utils/treeUtils.d.ts +25 -0
  228. package/lib/typescript/commonjs/src/web/utils/treeUtils.d.ts.map +1 -0
  229. package/lib/typescript/commonjs/src/web/utils/webStyleUtils.d.ts +7 -0
  230. package/lib/typescript/commonjs/src/web/utils/webStyleUtils.d.ts.map +1 -0
  231. package/lib/typescript/module/e2e/checks.test.d.ts +2 -0
  232. package/lib/typescript/module/e2e/checks.test.d.ts.map +1 -0
  233. package/lib/typescript/module/e2e/editing.test.d.ts +2 -0
  234. package/lib/typescript/module/e2e/editing.test.d.ts.map +1 -0
  235. package/lib/typescript/module/e2e/helpers.d.ts +23 -0
  236. package/lib/typescript/module/e2e/helpers.d.ts.map +1 -0
  237. package/lib/typescript/module/e2e/smoke.test.d.ts +2 -0
  238. package/lib/typescript/module/e2e/smoke.test.d.ts.map +1 -0
  239. package/lib/typescript/module/package.json +1 -0
  240. package/lib/typescript/module/src/MarcusTextInputDecoratorViewNativeComponent.d.ts +63 -0
  241. package/lib/typescript/module/src/MarcusTextInputDecoratorViewNativeComponent.d.ts.map +1 -0
  242. package/lib/typescript/module/src/MarkdownTextInput.d.ts +22 -0
  243. package/lib/typescript/module/src/MarkdownTextInput.d.ts.map +1 -0
  244. package/lib/typescript/module/src/MarkdownTextInput.web.d.ts +41 -0
  245. package/lib/typescript/module/src/MarkdownTextInput.web.d.ts.map +1 -0
  246. package/lib/typescript/module/src/NativeMarcusModule.d.ts +7 -0
  247. package/lib/typescript/module/src/NativeMarcusModule.d.ts.map +1 -0
  248. package/lib/typescript/module/src/commonTypes.d.ts +10 -0
  249. package/lib/typescript/module/src/commonTypes.d.ts.map +1 -0
  250. package/lib/typescript/module/src/index.d.ts +5 -0
  251. package/lib/typescript/module/src/index.d.ts.map +1 -0
  252. package/lib/typescript/module/src/parser/index.d.ts +8 -0
  253. package/lib/typescript/module/src/parser/index.d.ts.map +1 -0
  254. package/lib/typescript/module/src/parser/micromark-extension-mentions.d.ts +8 -0
  255. package/lib/typescript/module/src/parser/micromark-extension-mentions.d.ts.map +1 -0
  256. package/lib/typescript/module/src/rangeUtils.d.ts +6 -0
  257. package/lib/typescript/module/src/rangeUtils.d.ts.map +1 -0
  258. package/lib/typescript/module/src/styleUtils.d.ts +8 -0
  259. package/lib/typescript/module/src/styleUtils.d.ts.map +1 -0
  260. package/lib/typescript/module/src/web/InputHistory.d.ts +24 -0
  261. package/lib/typescript/module/src/web/InputHistory.d.ts.map +1 -0
  262. package/lib/typescript/module/src/web/utils/blockLayout.d.ts +41 -0
  263. package/lib/typescript/module/src/web/utils/blockLayout.d.ts.map +1 -0
  264. package/lib/typescript/module/src/web/utils/blockUtils.d.ts +33 -0
  265. package/lib/typescript/module/src/web/utils/blockUtils.d.ts.map +1 -0
  266. package/lib/typescript/module/src/web/utils/browserUtils.d.ts +7 -0
  267. package/lib/typescript/module/src/web/utils/browserUtils.d.ts.map +1 -0
  268. package/lib/typescript/module/src/web/utils/cursorUtils.d.ts +12 -0
  269. package/lib/typescript/module/src/web/utils/cursorUtils.d.ts.map +1 -0
  270. package/lib/typescript/module/src/web/utils/inputUtils.d.ts +12 -0
  271. package/lib/typescript/module/src/web/utils/inputUtils.d.ts.map +1 -0
  272. package/lib/typescript/module/src/web/utils/measureUtils.d.ts +22 -0
  273. package/lib/typescript/module/src/web/utils/measureUtils.d.ts.map +1 -0
  274. package/lib/typescript/module/src/web/utils/parserUtils.d.ts +44 -0
  275. package/lib/typescript/module/src/web/utils/parserUtils.d.ts.map +1 -0
  276. package/lib/typescript/module/src/web/utils/treeUtils.d.ts +25 -0
  277. package/lib/typescript/module/src/web/utils/treeUtils.d.ts.map +1 -0
  278. package/lib/typescript/module/src/web/utils/webStyleUtils.d.ts +7 -0
  279. package/lib/typescript/module/src/web/utils/webStyleUtils.d.ts.map +1 -0
  280. package/package.json +167 -0
  281. package/react-native.config.js +10 -0
  282. package/src/MarcusTextInputDecoratorViewNativeComponent.ts +77 -0
  283. package/src/MarkdownTextInput.tsx +139 -0
  284. package/src/MarkdownTextInput.web.tsx +958 -0
  285. package/src/NativeMarcusModule.ts +8 -0
  286. package/src/commonTypes.ts +30 -0
  287. package/src/global.d.ts +1 -0
  288. package/src/index.tsx +4 -0
  289. package/src/parser/index.ts +556 -0
  290. package/src/parser/micromark-extension-mentions.ts +82 -0
  291. package/src/rangeUtils.ts +68 -0
  292. package/src/react-native-web.d.ts +1 -0
  293. package/src/styleUtils.ts +100 -0
  294. package/src/web/InputHistory.ts +137 -0
  295. package/src/web/MarkdownTextInput.css +72 -0
  296. package/src/web/utils/blockLayout.ts +163 -0
  297. package/src/web/utils/blockUtils.ts +245 -0
  298. package/src/web/utils/browserUtils.ts +41 -0
  299. package/src/web/utils/cursorUtils.ts +204 -0
  300. package/src/web/utils/inputUtils.ts +206 -0
  301. package/src/web/utils/measureUtils.ts +0 -0
  302. package/src/web/utils/parserUtils.ts +813 -0
  303. package/src/web/utils/treeUtils.ts +149 -0
  304. package/src/web/utils/webStyleUtils.ts +69 -0
@@ -0,0 +1,509 @@
1
+ import UIKit
2
+
3
+ /// Applies markdown ranges to an attributed string as text attributes.
4
+ ///
5
+ /// Stateless by design: style and ranges are parameters rather than stored
6
+ /// properties. That is what lets the Fabric measure path call it concurrently
7
+ /// without a lock -- the previous design stored the style and parser id on a
8
+ /// shared object, so a setter on one thread could race a read on another.
9
+ @objc public final class MarkdownFormatter: NSObject {
10
+
11
+ /// Font resolution is injected rather than reached for, so this file carries
12
+ /// no React dependency and can be compiled on its own. The Objective-C entry
13
+ /// point in `MarkdownFormatter+React.swift` supplies the production one.
14
+ public static func format(
15
+ _ attributedString: NSMutableAttributedString,
16
+ defaultTextAttributes: [NSAttributedString.Key: Any],
17
+ ranges: [MarcusRange],
18
+ style: RCTMarcusStyle,
19
+ fonts: MarkdownFontProviding
20
+ ) {
21
+ let fullRange = NSRange(location: 0, length: attributedString.length)
22
+
23
+ attributedString.beginEditing()
24
+
25
+ attributedString.setAttributes(defaultTextAttributes, range: fullRange)
26
+
27
+ // Forces the swizzled `_textOf:equals:` into string-only comparison.
28
+ attributedString.addAttribute(.marcusText, value: true, range: fullRange)
29
+
30
+ // Containers arrive per line, outermost first, each preceded by the run of
31
+ // text its own marker takes up on that line. Laying a line out is a walk
32
+ // left to right: reserve a container's gutter, step over its marker, repeat.
33
+ var line = BlockLayout()
34
+ var pendingPrefix: NSRange?
35
+
36
+ for markdownRange in ranges {
37
+ if markdownRange.type == "block-prefix" {
38
+ pendingPrefix = markdownRange.range
39
+ continue
40
+ }
41
+
42
+ if let gutter = gutter(
43
+ for: markdownRange.type,
44
+ depth: Int(markdownRange.depth),
45
+ style: style
46
+ ) {
47
+ line.add(
48
+ markdownRange.type,
49
+ gutter: gutter,
50
+ depth: Int(markdownRange.depth),
51
+ prefix: pendingPrefix,
52
+ to: attributedString,
53
+ range: markdownRange.range,
54
+ defaultTextAttributes: defaultTextAttributes
55
+ )
56
+ pendingPrefix = nil
57
+ continue
58
+ }
59
+
60
+ apply(
61
+ markdownRange.type,
62
+ to: attributedString,
63
+ range: markdownRange.range,
64
+ depth: Int(markdownRange.depth),
65
+ style: style,
66
+ fonts: fonts
67
+ )
68
+ }
69
+
70
+ (attributedString.string as NSString).enumerateSubstrings(
71
+ in: fullRange,
72
+ options: [.byLines, .substringNotRequired]
73
+ ) { _, _, enclosingRange, _ in
74
+ applyBaselineOffset(to: attributedString, range: enclosingRange)
75
+ }
76
+
77
+ attributedString.fixAttributes(in: fullRange)
78
+
79
+ attributedString.endEditing()
80
+ }
81
+
82
+ // MARK: - Per-range attributes
83
+
84
+ private static func apply(
85
+ _ type: String,
86
+ to attributedString: NSMutableAttributedString,
87
+ range: NSRange,
88
+ depth: Int,
89
+ style: RCTMarcusStyle,
90
+ fonts: MarkdownFontProviding
91
+ ) {
92
+ switch type {
93
+ case "bold", "italic", "code", "pre", "heading", "emoji":
94
+ var font =
95
+ attributedString.attribute(
96
+ .font,
97
+ at: range.location,
98
+ effectiveRange: nil
99
+ ) as? UIFont
100
+ switch type {
101
+ case "bold":
102
+ font = fonts.update(font, weight: "bold")
103
+ case "italic":
104
+ font = fonts.update(font, style: "italic")
105
+ case "code":
106
+ font = fonts.update(
107
+ font,
108
+ family: style.codeFontFamily,
109
+ size: style.codeFontSize,
110
+ weight: nil
111
+ )
112
+ case "pre":
113
+ font = fonts.update(
114
+ font,
115
+ family: style.preFontFamily,
116
+ size: style.preFontSize,
117
+ weight: nil
118
+ )
119
+ case "heading":
120
+ // Level N is the base size scaled N-1 times, so a single pair of style
121
+ // values covers all six.
122
+ var size = style.headingFontSize
123
+ for _ in 1..<max(depth, 1) {
124
+ size *= style.headingScale
125
+ }
126
+ font = fonts.update(font, family: nil, size: size, weight: "bold")
127
+ case "emoji":
128
+ font = fonts.update(
129
+ font,
130
+ family: style.emojiFontFamily,
131
+ size: style.emojiFontSize,
132
+ weight: nil
133
+ )
134
+ default:
135
+ break
136
+ }
137
+ attributedString.addAttribute(.font, value: font as Any, range: range)
138
+ default:
139
+ break
140
+ }
141
+
142
+ switch type {
143
+ case "syntax":
144
+ attributedString.addAttribute(
145
+ .foregroundColor,
146
+ value: style.syntaxColor,
147
+ range: range
148
+ )
149
+
150
+ case "strikethrough":
151
+ attributedString.addAttribute(
152
+ .strikethroughStyle,
153
+ value: NSUnderlineStyle.single.rawValue,
154
+ range: range
155
+ )
156
+
157
+ case "code":
158
+ attributedString.addAttribute(
159
+ .foregroundColor,
160
+ value: style.codeColor,
161
+ range: range
162
+ )
163
+ attributedString.addAttribute(
164
+ .backgroundColor,
165
+ value: style.codeBackgroundColor,
166
+ range: range
167
+ )
168
+
169
+ case "mention-here":
170
+ applyMention(
171
+ to: attributedString,
172
+ range: range,
173
+ color: style.mentionHereColor,
174
+ backgroundColor: style.mentionHereBackgroundColor,
175
+ borderRadius: style.mentionHereBorderRadius
176
+ )
177
+
178
+ case "mention-user":
179
+ // TODO: change mention color when it mentions current user
180
+ applyMention(
181
+ to: attributedString,
182
+ range: range,
183
+ color: style.mentionUserColor,
184
+ backgroundColor: style.mentionUserBackgroundColor,
185
+ borderRadius: style.mentionUserBorderRadius
186
+ )
187
+
188
+ case "mention-report":
189
+ applyMention(
190
+ to: attributedString,
191
+ range: range,
192
+ color: style.mentionReportColor,
193
+ backgroundColor: style.mentionReportBackgroundColor,
194
+ borderRadius: style.mentionReportBorderRadius
195
+ )
196
+
197
+ case "link":
198
+ attributedString.addAttribute(
199
+ .underlineStyle,
200
+ value: NSUnderlineStyle.single.rawValue,
201
+ range: range
202
+ )
203
+ attributedString.addAttribute(
204
+ .foregroundColor,
205
+ value: style.linkColor,
206
+ range: range
207
+ )
208
+
209
+ case "pre":
210
+ attributedString.addAttribute(
211
+ .foregroundColor,
212
+ value: style.preColor,
213
+ range: range
214
+ )
215
+ // A fenced block's range starts with the newline after the opening fence;
216
+ // the background should not cover it.
217
+ let text = attributedString.string as NSString
218
+ let rangeForBackground =
219
+ text.character(at: range.location) == unichar(10)
220
+ ? NSRange(location: range.location + 1, length: range.length - 1)
221
+ : range
222
+ attributedString.addAttribute(
223
+ .backgroundColor,
224
+ value: style.preBackgroundColor,
225
+ range: rangeForBackground
226
+ )
227
+
228
+ default:
229
+ break
230
+ }
231
+ }
232
+
233
+ private static func applyMention(
234
+ to attributedString: NSMutableAttributedString,
235
+ range: NSRange,
236
+ color: UIColor,
237
+ backgroundColor: UIColor,
238
+ borderRadius: CGFloat
239
+ ) {
240
+ attributedString.addAttribute(.foregroundColor, value: color, range: range)
241
+
242
+ let textBackground = MarkdownTextBackground()
243
+ textBackground.color = backgroundColor
244
+ textBackground.borderRadius = borderRadius
245
+ attributedString.addAttribute(
246
+ .marcusTextBackground,
247
+ value: textBackground,
248
+ range: range
249
+ )
250
+ }
251
+
252
+ // MARK: - Block layout
253
+
254
+ /// The gutter a container reserves for itself, or nil if the type is not one.
255
+ private static func gutter(
256
+ for type: String,
257
+ depth: Int,
258
+ style: RCTMarcusStyle
259
+ ) -> CGFloat? {
260
+ switch type {
261
+ case "blockquote":
262
+ return (style.blockquoteMarginLeft + style.blockquoteBorderWidth
263
+ + style.blockquotePaddingLeft) * CGFloat(depth)
264
+ case "list-ordered":
265
+ return (style.orderedListMarginLeft + style.orderedListPaddingLeft)
266
+ * CGFloat(depth)
267
+ case "list-unordered":
268
+ return (style.unorderedListMarginLeft + style.unorderedListPaddingLeft)
269
+ * CGFloat(depth)
270
+ default:
271
+ return nil
272
+ }
273
+ }
274
+
275
+ /// Places the containers of one line, left to right.
276
+ ///
277
+ /// Each container reserves a gutter and is then followed by its own marker,
278
+ /// which is text and so has to be stepped over rather than reserved: the
279
+ /// container nested inside it starts after the marker, not in front of it.
280
+ /// That is what puts a quote's ribbons after a list bullet, and a list's
281
+ /// indent after a quote's `>`.
282
+ ///
283
+ /// A line continuing a block carries no marker of its own, and reuses the one
284
+ /// that opened it -- otherwise its text, and any ribbon beside it, would sit
285
+ /// at a different offset than the line above and break the block in two.
286
+ private struct BlockLayout {
287
+ /// Last marker seen for each container type, for the lines that continue it.
288
+ private var markers: [String: NSRange] = [:]
289
+ private var lineStart = NSNotFound
290
+ /// Offset reached so far, from the paragraph's own left edge.
291
+ private var offset: CGFloat = 0
292
+ /// Where the line's first marker begins, if it has one. The text starts
293
+ /// there; everything past it is held open with padding instead.
294
+ private var textStart: CGFloat?
295
+ /// Marker of the container placed most recently, if it is on this line and
296
+ /// so has something to pad.
297
+ private var padded: NSRange?
298
+
299
+ mutating func add(
300
+ _ type: String,
301
+ gutter: CGFloat,
302
+ depth: Int,
303
+ prefix: NSRange?,
304
+ to attributedString: NSMutableAttributedString,
305
+ range: NSRange,
306
+ defaultTextAttributes: [NSAttributedString.Key: Any]
307
+ ) {
308
+ if range.location != lineStart {
309
+ lineStart = range.location
310
+ offset = MarkdownFormatter.baseIndent(
311
+ of: attributedString,
312
+ at: range.location,
313
+ defaultTextAttributes: defaultTextAttributes
314
+ )
315
+ textStart = nil
316
+ padded = nil
317
+ }
318
+
319
+ if let padded {
320
+ MarkdownFormatter.pad(padded, by: gutter, in: attributedString)
321
+ }
322
+
323
+ if type == "blockquote" {
324
+ attributedString.addAttribute(
325
+ .marcusBlockquoteDepth,
326
+ value: depth,
327
+ range: range
328
+ )
329
+ attributedString.addAttribute(
330
+ .marcusBlockquoteIndent,
331
+ value: offset,
332
+ range: range
333
+ )
334
+ }
335
+
336
+ offset += gutter
337
+
338
+ if let prefix {
339
+ markers[type] = prefix
340
+ if textStart == nil {
341
+ textStart = offset
342
+ }
343
+ }
344
+
345
+ if let marker = prefix ?? markers[type] {
346
+ offset += MarkdownFormatter.measure(marker, in: attributedString)
347
+ }
348
+
349
+ padded = prefix
350
+ MarkdownFormatter.setIndent(
351
+ firstLine: textStart ?? offset,
352
+ rest: offset,
353
+ of: attributedString,
354
+ range: range,
355
+ defaultTextAttributes: defaultTextAttributes
356
+ )
357
+ }
358
+ }
359
+
360
+ // MARK: - Indents
361
+
362
+ private static func baseIndent(
363
+ of attributedString: NSAttributedString,
364
+ at location: Int,
365
+ defaultTextAttributes: [NSAttributedString.Key: Any]
366
+ ) -> CGFloat {
367
+ paragraphStyle(
368
+ of: attributedString,
369
+ at: location,
370
+ defaultTextAttributes: defaultTextAttributes
371
+ ).headIndent
372
+ }
373
+
374
+ private static func setIndent(
375
+ firstLine: CGFloat,
376
+ rest: CGFloat,
377
+ of attributedString: NSMutableAttributedString,
378
+ range: NSRange,
379
+ defaultTextAttributes: [NSAttributedString.Key: Any]
380
+ ) {
381
+ let style = paragraphStyle(
382
+ of: attributedString,
383
+ at: range.location,
384
+ defaultTextAttributes: defaultTextAttributes
385
+ )
386
+ style.firstLineHeadIndent = firstLine
387
+ style.headIndent = rest
388
+
389
+ attributedString.addAttribute(.paragraphStyle, value: style, range: range)
390
+ }
391
+
392
+ private static func paragraphStyle(
393
+ of attributedString: NSAttributedString,
394
+ at location: Int,
395
+ defaultTextAttributes: [NSAttributedString.Key: Any]
396
+ ) -> NSMutableParagraphStyle {
397
+ let existing =
398
+ attributedString.attribute(
399
+ .paragraphStyle,
400
+ at: location,
401
+ effectiveRange: nil
402
+ ) as? NSParagraphStyle
403
+ ?? defaultTextAttributes[.paragraphStyle] as? NSParagraphStyle
404
+
405
+ return (existing?.mutableCopy() as? NSMutableParagraphStyle)
406
+ ?? NSMutableParagraphStyle()
407
+ }
408
+
409
+ // MARK: - Measurement
410
+
411
+ /// Opens `width` of space after `range` without touching its glyphs, so a
412
+ /// nested container's gutter can sit between one marker and the next.
413
+ private static func pad(
414
+ _ range: NSRange,
415
+ by width: CGFloat,
416
+ in attributedString: NSMutableAttributedString
417
+ ) {
418
+ guard width > 0, range.length > 0 else { return }
419
+
420
+ attributedString.addAttribute(
421
+ .kern,
422
+ value: width,
423
+ range: NSRange(location: NSMaxRange(range) - 1, length: 1)
424
+ )
425
+ }
426
+
427
+ /// Width `range` takes up mid-line, rounded up to a whole point.
428
+ ///
429
+ /// A sentinel is appended and its width taken back off again because `size()`
430
+ /// measures the range as a line of its own and drops trailing whitespace --
431
+ /// which a list-item prefix always ends in.
432
+ ///
433
+ /// Rounding is what keeps a quote's ribbon in one piece. TextKit snaps every
434
+ /// line's origin to the device pixel grid, so a line indented past a marker
435
+ /// and a line indented past nothing land on different sides of a fractional
436
+ /// width, and the bar drawn beside them breaks by a pixel. A whole point is a
437
+ /// whole number of pixels at every screen scale, so there is nothing left to
438
+ /// snap. Rounding up rather than down also keeps the gap from eating into the
439
+ /// marker it was measured from.
440
+ private static func measure(
441
+ _ range: NSRange,
442
+ in attributedString: NSAttributedString
443
+ ) -> CGFloat {
444
+ let text = NSMutableAttributedString(
445
+ attributedString: attributedString.attributedSubstring(from: range)
446
+ )
447
+
448
+ guard text.length > 0 else { return 0 }
449
+
450
+ let full = NSRange(location: 0, length: text.length)
451
+ // Indents belong to the paragraph, not to the run being measured, and the
452
+ // padding opened after the marker is the next container's gutter rather
453
+ // than part of the marker itself.
454
+ text.removeAttribute(.paragraphStyle, range: full)
455
+ text.removeAttribute(.kern, range: full)
456
+
457
+ let sentinel = NSAttributedString(
458
+ string: "|",
459
+ attributes: text.attributes(at: text.length - 1, effectiveRange: nil)
460
+ )
461
+ text.append(sentinel)
462
+
463
+ return (text.size().width - sentinel.size().width).rounded(.up)
464
+ }
465
+
466
+ // MARK: - Baseline
467
+
468
+ /// Re-centres text vertically within an explicit `lineHeight` after the fonts
469
+ /// above have changed the natural line height.
470
+ private static func applyBaselineOffset(
471
+ to attributedText: NSMutableAttributedString,
472
+ range: NSRange
473
+ ) {
474
+ var maximumLineHeight: CGFloat = 0
475
+ attributedText.enumerateAttribute(
476
+ .paragraphStyle,
477
+ in: range,
478
+ options: .longestEffectiveRangeNotRequired
479
+ ) { value, _, _ in
480
+ guard let paragraphStyle = value as? NSParagraphStyle else { return }
481
+ maximumLineHeight = max(
482
+ paragraphStyle.maximumLineHeight,
483
+ maximumLineHeight
484
+ )
485
+ }
486
+
487
+ // `lineHeight` was not specified, nothing to do.
488
+ guard maximumLineHeight != 0 else { return }
489
+
490
+ var maximumFontLineHeight: CGFloat = 0
491
+ attributedText.enumerateAttribute(
492
+ .font,
493
+ in: range,
494
+ options: .longestEffectiveRangeNotRequired
495
+ ) { value, _, _ in
496
+ guard let font = value as? UIFont else { return }
497
+ maximumFontLineHeight = max(font.lineHeight, maximumFontLineHeight)
498
+ }
499
+
500
+ guard maximumLineHeight >= maximumFontLineHeight else { return }
501
+
502
+ let baselineOffset = (maximumLineHeight - maximumFontLineHeight) / 2.0
503
+ attributedText.addAttribute(
504
+ .baselineOffset,
505
+ value: baselineOffset,
506
+ range: range
507
+ )
508
+ }
509
+ }
@@ -0,0 +1,10 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ // RCTLogWarn is a variadic C macro, so it cannot be called from Swift. This is
6
+ // the thinnest possible shim over it, kept so parser warnings still reach LogBox.
7
+ FOUNDATION_EXPORT void
8
+ MarkdownLogWarn(NSString *message) NS_SWIFT_NAME(markdownLogWarn(_:));
9
+
10
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,6 @@
1
+ #import <RNMarcus/MarkdownLog.h>
2
+
3
+ #import <React/RCTLog.h>
4
+
5
+ void
6
+ MarkdownLogWarn(NSString *message) { RCTLogWarn(@"%@", message); }