react-native-nitro-markdown 0.5.2 → 0.5.4

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 (178) hide show
  1. package/README.md +311 -669
  2. package/android/CMakeLists.txt +8 -1
  3. package/android/build.gradle +9 -2
  4. package/android/consumer-rules.pro +31 -0
  5. package/android/gradle.properties +2 -0
  6. package/android/src/main/java/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSession.kt +68 -22
  7. package/android/src/main/java/com/nitromarkdown/NitroMarkdownPackage.kt +6 -18
  8. package/cpp/bindings/HybridMarkdownParser.cpp +40 -12
  9. package/cpp/bindings/HybridMarkdownParser.hpp +4 -4
  10. package/cpp/bindings/HybridMarkdownSession.cpp +2 -0
  11. package/cpp/core/MD4CParser.cpp +147 -86
  12. package/cpp/core/MarkdownSessionCore.cpp +2 -0
  13. package/cpp/core/MarkdownTypes.hpp +1 -1
  14. package/ios/HybridMarkdownSession.swift +89 -46
  15. package/lib/commonjs/headless.js +34 -8
  16. package/lib/commonjs/headless.js.map +1 -1
  17. package/lib/commonjs/index.js +48 -38
  18. package/lib/commonjs/index.js.map +1 -1
  19. package/lib/commonjs/markdown-stream.js +1 -1
  20. package/lib/commonjs/markdown-stream.js.map +1 -1
  21. package/lib/commonjs/markdown.js +57 -16
  22. package/lib/commonjs/markdown.js.map +1 -1
  23. package/lib/commonjs/renderers/blockquote.js +15 -13
  24. package/lib/commonjs/renderers/blockquote.js.map +1 -1
  25. package/lib/commonjs/renderers/code.js +58 -54
  26. package/lib/commonjs/renderers/code.js.map +1 -1
  27. package/lib/commonjs/renderers/heading.js +48 -46
  28. package/lib/commonjs/renderers/heading.js.map +1 -1
  29. package/lib/commonjs/renderers/horizontal-rule.js +10 -8
  30. package/lib/commonjs/renderers/horizontal-rule.js.map +1 -1
  31. package/lib/commonjs/renderers/image.js +18 -4
  32. package/lib/commonjs/renderers/image.js.map +1 -1
  33. package/lib/commonjs/renderers/link.js +7 -2
  34. package/lib/commonjs/renderers/link.js.map +1 -1
  35. package/lib/commonjs/renderers/list.js +75 -68
  36. package/lib/commonjs/renderers/list.js.map +1 -1
  37. package/lib/commonjs/renderers/math.js +8 -5
  38. package/lib/commonjs/renderers/math.js.map +1 -1
  39. package/lib/commonjs/renderers/paragraph.js +15 -13
  40. package/lib/commonjs/renderers/paragraph.js.map +1 -1
  41. package/lib/commonjs/renderers/style-cache.js +14 -0
  42. package/lib/commonjs/renderers/style-cache.js.map +1 -0
  43. package/lib/commonjs/renderers/table/cell-content.js +1 -1
  44. package/lib/commonjs/renderers/table/cell-content.js.map +1 -1
  45. package/lib/commonjs/renderers/table/index.js +17 -6
  46. package/lib/commonjs/renderers/table/index.js.map +1 -1
  47. package/lib/commonjs/theme.js +7 -7
  48. package/lib/commonjs/theme.js.map +1 -1
  49. package/lib/commonjs/utils/code-highlight.js +24 -25
  50. package/lib/commonjs/utils/code-highlight.js.map +1 -1
  51. package/lib/module/headless.js +35 -7
  52. package/lib/module/headless.js.map +1 -1
  53. package/lib/module/index.js +1 -1
  54. package/lib/module/index.js.map +1 -1
  55. package/lib/module/markdown-stream.js +1 -1
  56. package/lib/module/markdown-stream.js.map +1 -1
  57. package/lib/module/markdown.js +58 -17
  58. package/lib/module/markdown.js.map +1 -1
  59. package/lib/module/renderers/blockquote.js +15 -13
  60. package/lib/module/renderers/blockquote.js.map +1 -1
  61. package/lib/module/renderers/code.js +58 -54
  62. package/lib/module/renderers/code.js.map +1 -1
  63. package/lib/module/renderers/heading.js +48 -46
  64. package/lib/module/renderers/heading.js.map +1 -1
  65. package/lib/module/renderers/horizontal-rule.js +10 -8
  66. package/lib/module/renderers/horizontal-rule.js.map +1 -1
  67. package/lib/module/renderers/image.js +19 -5
  68. package/lib/module/renderers/image.js.map +1 -1
  69. package/lib/module/renderers/link.js +7 -2
  70. package/lib/module/renderers/link.js.map +1 -1
  71. package/lib/module/renderers/list.js +75 -68
  72. package/lib/module/renderers/list.js.map +1 -1
  73. package/lib/module/renderers/math.js +8 -5
  74. package/lib/module/renderers/math.js.map +1 -1
  75. package/lib/module/renderers/paragraph.js +15 -13
  76. package/lib/module/renderers/paragraph.js.map +1 -1
  77. package/lib/module/renderers/style-cache.js +10 -0
  78. package/lib/module/renderers/style-cache.js.map +1 -0
  79. package/lib/module/renderers/table/cell-content.js +1 -1
  80. package/lib/module/renderers/table/cell-content.js.map +1 -1
  81. package/lib/module/renderers/table/index.js +17 -6
  82. package/lib/module/renderers/table/index.js.map +1 -1
  83. package/lib/module/theme.js +7 -7
  84. package/lib/module/theme.js.map +1 -1
  85. package/lib/module/utils/code-highlight.js +24 -25
  86. package/lib/module/utils/code-highlight.js.map +1 -1
  87. package/lib/typescript/commonjs/Markdown.nitro.d.ts +1 -0
  88. package/lib/typescript/commonjs/Markdown.nitro.d.ts.map +1 -1
  89. package/lib/typescript/commonjs/headless.d.ts +10 -2
  90. package/lib/typescript/commonjs/headless.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/index.d.ts +3 -2
  92. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  93. package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
  94. package/lib/typescript/commonjs/markdown.d.ts +8 -3
  95. package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/renderers/blockquote.d.ts +1 -1
  97. package/lib/typescript/commonjs/renderers/blockquote.d.ts.map +1 -1
  98. package/lib/typescript/commonjs/renderers/code.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/renderers/heading.d.ts +1 -1
  100. package/lib/typescript/commonjs/renderers/heading.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts +1 -1
  102. package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts.map +1 -1
  103. package/lib/typescript/commonjs/renderers/image.d.ts.map +1 -1
  104. package/lib/typescript/commonjs/renderers/link.d.ts.map +1 -1
  105. package/lib/typescript/commonjs/renderers/list.d.ts +1 -1
  106. package/lib/typescript/commonjs/renderers/list.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/renderers/math.d.ts +1 -1
  108. package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -1
  109. package/lib/typescript/commonjs/renderers/paragraph.d.ts +1 -1
  110. package/lib/typescript/commonjs/renderers/paragraph.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/renderers/style-cache.d.ts +3 -0
  112. package/lib/typescript/commonjs/renderers/style-cache.d.ts.map +1 -0
  113. package/lib/typescript/commonjs/renderers/table/cell-content.d.ts +4 -3
  114. package/lib/typescript/commonjs/renderers/table/cell-content.d.ts.map +1 -1
  115. package/lib/typescript/commonjs/renderers/table/index.d.ts.map +1 -1
  116. package/lib/typescript/commonjs/theme.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/utils/code-highlight.d.ts +1 -1
  118. package/lib/typescript/commonjs/utils/code-highlight.d.ts.map +1 -1
  119. package/lib/typescript/module/Markdown.nitro.d.ts +1 -0
  120. package/lib/typescript/module/Markdown.nitro.d.ts.map +1 -1
  121. package/lib/typescript/module/headless.d.ts +10 -2
  122. package/lib/typescript/module/headless.d.ts.map +1 -1
  123. package/lib/typescript/module/index.d.ts +3 -2
  124. package/lib/typescript/module/index.d.ts.map +1 -1
  125. package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
  126. package/lib/typescript/module/markdown.d.ts +8 -3
  127. package/lib/typescript/module/markdown.d.ts.map +1 -1
  128. package/lib/typescript/module/renderers/blockquote.d.ts +1 -1
  129. package/lib/typescript/module/renderers/blockquote.d.ts.map +1 -1
  130. package/lib/typescript/module/renderers/code.d.ts.map +1 -1
  131. package/lib/typescript/module/renderers/heading.d.ts +1 -1
  132. package/lib/typescript/module/renderers/heading.d.ts.map +1 -1
  133. package/lib/typescript/module/renderers/horizontal-rule.d.ts +1 -1
  134. package/lib/typescript/module/renderers/horizontal-rule.d.ts.map +1 -1
  135. package/lib/typescript/module/renderers/image.d.ts.map +1 -1
  136. package/lib/typescript/module/renderers/link.d.ts.map +1 -1
  137. package/lib/typescript/module/renderers/list.d.ts +1 -1
  138. package/lib/typescript/module/renderers/list.d.ts.map +1 -1
  139. package/lib/typescript/module/renderers/math.d.ts +1 -1
  140. package/lib/typescript/module/renderers/math.d.ts.map +1 -1
  141. package/lib/typescript/module/renderers/paragraph.d.ts +1 -1
  142. package/lib/typescript/module/renderers/paragraph.d.ts.map +1 -1
  143. package/lib/typescript/module/renderers/style-cache.d.ts +3 -0
  144. package/lib/typescript/module/renderers/style-cache.d.ts.map +1 -0
  145. package/lib/typescript/module/renderers/table/cell-content.d.ts +4 -3
  146. package/lib/typescript/module/renderers/table/cell-content.d.ts.map +1 -1
  147. package/lib/typescript/module/renderers/table/index.d.ts.map +1 -1
  148. package/lib/typescript/module/theme.d.ts.map +1 -1
  149. package/lib/typescript/module/utils/code-highlight.d.ts +1 -1
  150. package/lib/typescript/module/utils/code-highlight.d.ts.map +1 -1
  151. package/nitro.json +12 -3
  152. package/nitrogen/generated/android/NitroMarkdownOnLoad.cpp +2 -2
  153. package/nitrogen/generated/android/c++/JFunc_void.hpp +2 -2
  154. package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +2 -2
  155. package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.hpp +2 -2
  156. package/nitrogen/generated/ios/NitroMarkdown+autolinking.rb +2 -0
  157. package/nitrogen/generated/shared/c++/ParserOptions.hpp +6 -2
  158. package/package.json +8 -6
  159. package/react-native-nitro-markdown.podspec +3 -0
  160. package/src/Markdown.nitro.ts +1 -0
  161. package/src/headless.ts +58 -8
  162. package/src/index.ts +16 -2
  163. package/src/markdown-stream.tsx +1 -0
  164. package/src/markdown.tsx +108 -23
  165. package/src/renderers/blockquote.tsx +22 -17
  166. package/src/renderers/code.tsx +76 -57
  167. package/src/renderers/heading.tsx +60 -54
  168. package/src/renderers/horizontal-rule.tsx +17 -12
  169. package/src/renderers/image.tsx +24 -5
  170. package/src/renderers/link.tsx +8 -2
  171. package/src/renderers/list.tsx +93 -74
  172. package/src/renderers/math.tsx +14 -5
  173. package/src/renderers/paragraph.tsx +22 -17
  174. package/src/renderers/style-cache.ts +14 -0
  175. package/src/renderers/table/cell-content.tsx +15 -4
  176. package/src/renderers/table/index.tsx +30 -13
  177. package/src/theme.ts +34 -14
  178. package/src/utils/code-highlight.ts +133 -44
@@ -1,4 +1,4 @@
1
- cmake_minimum_required(VERSION 3.22.1)
1
+ cmake_minimum_required(VERSION 3.18.1...3.28)
2
2
  project(NitroMarkdown)
3
3
 
4
4
  set(CMAKE_CXX_STANDARD 20)
@@ -13,6 +13,7 @@ set(CPP_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../cpp")
13
13
  file(GLOB MD4C_SOURCES "${CPP_ROOT}/md4c/*.c")
14
14
  file(GLOB CORE_SOURCES "${CPP_ROOT}/core/*.cpp")
15
15
  file(GLOB BINDING_SOURCES "${CPP_ROOT}/bindings/*.cpp")
16
+ list(FILTER CORE_SOURCES EXCLUDE REGEX ".*Test\\.cpp$")
16
17
 
17
18
  # Create the shared library with our sources
18
19
  add_library(${PROJECT_NAME} SHARED
@@ -38,3 +39,9 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE
38
39
  # Include Nitro autolinking (adds nitrogen sources, definitions, and links)
39
40
  include(${CMAKE_CURRENT_SOURCE_DIR}/../nitrogen/generated/android/NitroMarkdown+autolinking.cmake)
40
41
 
42
+ # L5: Per-build-type optimization flags. Release builds get full optimization and strip debug
43
+ # symbols from the binary. Debug builds disable optimization for easier debugging.
44
+ target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE
45
+ $<$<CONFIG:Release>:-O3 -DNDEBUG>
46
+ $<$<CONFIG:Debug>:-O0 -g>
47
+ )
@@ -11,7 +11,10 @@ buildscript {
11
11
 
12
12
  def reactNativeArchitectures() {
13
13
  def value = rootProject.getProperties().get("reactNativeArchitectures")
14
- return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
14
+ // L4: Default to 64-bit only. 32-bit ABIs (armeabi-v7a, x86) are excluded from the default
15
+ // because all Android devices on API 24+ support 64-bit, and 32-bit increases APK size with no
16
+ // practical benefit for this library. Override via gradle.properties if needed.
17
+ return value ? value.split(",") : ["arm64-v8a", "x86_64"]
15
18
  }
16
19
 
17
20
  def isNewArchitectureEnabled() {
@@ -44,10 +47,14 @@ android {
44
47
  minSdkVersion getExtOrIntegerDefault("minSdkVersion")
45
48
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
46
49
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
50
+ consumerProguardFiles 'consumer-rules.pro'
47
51
 
48
52
  externalNativeBuild {
49
53
  cmake {
50
- cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all"
54
+ // L3/L5: -fstack-protector-strong replaces -fstack-protector-all (better coverage/perf
55
+ // tradeoff). -Wformat/-Werror=format-security guard against format-string exploits.
56
+ // -D_FORTIFY_SOURCE=2 enables glibc/Bionic source fortification for buffer overflows.
57
+ cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-strong -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2"
51
58
  arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
52
59
  abiFilters (*reactNativeArchitectures())
53
60
  }
@@ -0,0 +1,31 @@
1
+ # react-native-nitro-markdown consumer ProGuard/R8 rules
2
+ # Preserve all Nitro Hybrid Objects and JNI-facing Kotlin classes
3
+ -keep class com.margelo.nitro.com.nitromarkdown.** { *; }
4
+ -keep class com.nitromarkdown.** { *; }
5
+
6
+ # C5: Explicitly preserve nitrogen-generated Func_* wrapper classes accessed via JNI reflection.
7
+ # These are already covered by the wildcard above, but explicit rules guard against future
8
+ # refactors that might narrow the wildcard, and make the intent clear to ProGuard/R8.
9
+ -keep class com.margelo.nitro.com.nitromarkdown.Func_void { *; }
10
+ -keep class com.margelo.nitro.com.nitromarkdown.Func_void_cxx { *; }
11
+ -keep class com.margelo.nitro.com.nitromarkdown.Func_void_java { *; }
12
+ -keep class com.margelo.nitro.com.nitromarkdown.Func_void_double_double { *; }
13
+ -keep class com.margelo.nitro.com.nitromarkdown.Func_void_double_double_cxx { *; }
14
+ -keep class com.margelo.nitro.com.nitromarkdown.Func_void_double_double_java { *; }
15
+ -keepclassmembers class com.margelo.nitro.com.nitromarkdown.Func_** {
16
+ <init>(...);
17
+ void invoke(...);
18
+ *;
19
+ }
20
+
21
+ # Preserve JNI-registered native methods
22
+ -keepclasseswithmembernames class * {
23
+ native <methods>;
24
+ }
25
+
26
+ # Preserve Facebook JNI annotations
27
+ -keep @com.facebook.proguard.annotations.DoNotStrip class * { *; }
28
+ -keepclassmembers class * {
29
+ @com.facebook.proguard.annotations.DoNotStrip *;
30
+ }
31
+ -keep @com.facebook.react.bridge.ReactModule class * { *; }
@@ -1,4 +1,6 @@
1
1
  NitroMarkdown_compileSdkVersion=34
2
+ # M3: minSdkVersion kept at 24 (Android 7.0) to match the example app's minimum SDK requirement.
3
+ # Bumping to 26 would be preferable for security (full ASLR, etc.) but would break example builds.
2
4
  NitroMarkdown_minSdkVersion=24
3
5
  NitroMarkdown_targetSdkVersion=34
4
6
  NitroMarkdown_ndkVersion=27.1.12297006
@@ -1,26 +1,38 @@
1
1
  package com.margelo.nitro.com.nitromarkdown
2
2
 
3
+ import androidx.annotation.GuardedBy
4
+
3
5
  class HybridMarkdownSession : HybridMarkdownSessionSpec() {
6
+ @GuardedBy("lock")
4
7
  private var buffer = StringBuilder()
8
+
9
+ @GuardedBy("lock")
5
10
  private val listeners = mutableMapOf<Long, (Double, Double) -> Unit>()
11
+
12
+ @GuardedBy("lock")
6
13
  private var nextListenerId = 0L
7
- private val lock = Any()
8
14
 
9
- override var highlightPosition: Double = 0.0
10
- set(value) {
11
- synchronized(lock) { field = value }
12
- // No notify for highlighting to avoid flood
13
- }
15
+ private val lock = Any()
14
16
 
17
+ @Volatile
18
+ private var isDestroyed = false
15
19
 
20
+ @GuardedBy("lock")
21
+ override var highlightPosition: Double = 0.0
22
+ get() = synchronized(lock) { field }
23
+ set(value) = synchronized(lock) { field = value }
24
+ // No notify for highlighting to avoid flood
16
25
 
17
26
  override val memorySize: Long
18
- get() = buffer.length.toLong()
27
+ get() = synchronized(lock) { buffer.length.toLong() }
19
28
 
20
29
  override fun append(chunk: String): Double {
21
30
  val from: Int
22
31
  val to: Int
23
32
  synchronized(lock) {
33
+ if (buffer.length + chunk.length > MAX_BUFFER_SIZE) {
34
+ throw IllegalArgumentException("Buffer size limit exceeded (max ${MAX_BUFFER_SIZE} chars)")
35
+ }
24
36
  from = buffer.length
25
37
  buffer.append(chunk)
26
38
  to = buffer.length
@@ -50,50 +62,84 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
50
62
  }
51
63
 
52
64
  override fun getTextRange(from: Double, to: Double): String {
65
+ if (!from.isFinite() || !to.isFinite() || from < 0.0 || to < 0.0 || from > to) return ""
53
66
  synchronized(lock) {
54
- val start = from.toInt().coerceIn(0, buffer.length)
55
- val end = to.toInt().coerceIn(start, buffer.length)
67
+ val start = from.toLong().coerceIn(0L, buffer.length.toLong()).toInt()
68
+ val end = to.toLong().coerceIn(start.toLong(), buffer.length.toLong()).toInt()
56
69
  return buffer.substring(start, end)
57
70
  }
58
71
  }
59
72
 
60
73
  override fun addListener(listener: (Double, Double) -> Unit): () -> Unit {
61
- val id: Long
62
74
  synchronized(lock) {
63
- id = nextListenerId++
75
+ if (isDestroyed) throw IllegalStateException("HybridMarkdownSession is destroyed")
76
+ val id = nextListenerId++
64
77
  listeners[id] = listener
65
- }
66
- return {
67
- synchronized(lock) {
68
- listeners.remove(id)
69
- }
78
+ return { synchronized(lock) { listeners.remove(id) } }
70
79
  }
71
80
  }
72
81
 
73
82
  override fun reset(text: String) {
74
83
  synchronized(lock) {
75
84
  buffer.replace(0, buffer.length, text)
85
+ highlightPosition = 0.0
76
86
  }
77
87
  notifyListeners(0.0, text.length.toDouble())
78
88
  }
79
89
 
80
90
  override fun replace(from: Double, to: Double, text: String): Double {
91
+ require(from.isFinite() && to.isFinite() && from >= 0.0 && to >= 0.0 && to >= from) {
92
+ "Invalid range: from=$from and to=$to must be finite, from must be >= 0, and to must be >= from"
93
+ }
81
94
  val newLength: Double
95
+ val notifyFrom: Double
96
+ val notifyTo: Double
82
97
  synchronized(lock) {
83
- val start = from.toInt().coerceIn(0, buffer.length)
84
- val end = to.toInt().coerceIn(start, buffer.length)
98
+ val start = from.toLong().coerceIn(0L, buffer.length.toLong()).toInt()
99
+ val end = to.toLong().coerceIn(start.toLong(), buffer.length.toLong()).toInt()
85
100
  buffer.replace(start, end, text)
86
101
  newLength = buffer.length.toDouble()
102
+ notifyFrom = start.toDouble()
103
+ notifyTo = start.toDouble() + text.length.toDouble()
87
104
  }
88
- notifyListeners(from, from + text.length.toDouble())
105
+ notifyListeners(notifyFrom, notifyTo)
89
106
  return newLength
90
107
  }
91
108
 
92
109
  private fun notifyListeners(from: Double, to: Double) {
93
- val currentListeners: Collection<(Double, Double) -> Unit>
110
+ val snapshot: List<(Double, Double) -> Unit>
111
+ synchronized(lock) {
112
+ snapshot = listeners.values.toList()
113
+ }
114
+ for (listener in snapshot) {
115
+ try {
116
+ listener(from, to)
117
+ } catch (e: Throwable) {
118
+ android.util.Log.e(TAG, "Listener callback threw an exception", e)
119
+ }
120
+ }
121
+ }
122
+
123
+ fun onDestroyed() {
94
124
  synchronized(lock) {
95
- currentListeners = listeners.values.toList()
125
+ isDestroyed = true
126
+ listeners.clear()
96
127
  }
97
- currentListeners.forEach { it(from, to) }
128
+ }
129
+
130
+ override fun dispose() {
131
+ onDestroyed()
132
+ super.dispose()
133
+ }
134
+
135
+ // H6: The C++ generated file (JHybridMarkdownSessionSpec.cpp) is marked DO NOT MODIFY and
136
+ // cannot be edited. However, fbjni (used by Nitro) automatically propagates Java/Kotlin
137
+ // exceptions thrown across the JNI boundary as C++ exceptions. Therefore, an
138
+ // IllegalArgumentException thrown in append() will be rethrown on the C++ side without
139
+ // requiring manual JNI exception checks in the generated code.
140
+
141
+ companion object {
142
+ private const val TAG = "HybridMarkdownSession"
143
+ private const val MAX_BUFFER_SIZE = 10 * 1024 * 1024 // 10 MB
98
144
  }
99
145
  }
@@ -1,28 +1,16 @@
1
1
  package com.nitromarkdown
2
2
 
3
- import com.facebook.react.BaseReactPackage
3
+ import com.facebook.react.ReactPackage
4
4
  import com.facebook.react.bridge.NativeModule
5
5
  import com.facebook.react.bridge.ReactApplicationContext
6
- import com.facebook.react.module.model.ReactModuleInfoProvider
7
6
  import com.facebook.react.uimanager.ViewManager
8
7
  import com.margelo.nitro.com.nitromarkdown.NitroMarkdownOnLoad
9
8
 
10
- class NitroMarkdownPackage : BaseReactPackage() {
11
- companion object {
12
- init {
13
- NitroMarkdownOnLoad.initializeNative()
14
- }
9
+ class NitroMarkdownPackage : ReactPackage {
10
+ init {
11
+ NitroMarkdownOnLoad.initializeNative()
15
12
  }
16
13
 
17
- override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
18
- return null
19
- }
20
-
21
- override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
22
- return ReactModuleInfoProvider { emptyMap() }
23
- }
24
-
25
- override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
26
- return emptyList()
27
- }
14
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> = emptyList()
15
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> = emptyList()
28
16
  }
@@ -79,6 +79,37 @@ inline void appendBoolField(std::string& output, const char* key, bool value) {
79
79
  output += value ? "true" : "false";
80
80
  }
81
81
 
82
+ static constexpr size_t kMaxEstimatedSize = 64 * 1024 * 1024; // 64 MB cap
83
+
84
+ static size_t estimateJsonSize(const std::shared_ptr<InternalMarkdownNode>& node) noexcept {
85
+ if (!node) return 0;
86
+ size_t size = 64; // base overhead per node (type, beg, end, braces)
87
+ auto safeAdd = [](size_t a, size_t b) -> size_t {
88
+ return (b > kMaxEstimatedSize - a) ? kMaxEstimatedSize : a + b;
89
+ };
90
+ if (node->content && size < kMaxEstimatedSize) {
91
+ size = safeAdd(size, node->content->size());
92
+ }
93
+ if (node->href && size < kMaxEstimatedSize) {
94
+ size = safeAdd(size, node->href->size() + 10);
95
+ }
96
+ if (node->title && size < kMaxEstimatedSize) {
97
+ size = safeAdd(size, node->title->size() + 10);
98
+ }
99
+ if (node->alt && size < kMaxEstimatedSize) {
100
+ size = safeAdd(size, node->alt->size() + 8);
101
+ }
102
+ if (node->language && size < kMaxEstimatedSize) {
103
+ size = safeAdd(size, node->language->size() + 12);
104
+ }
105
+ for (const auto& child : node->children) {
106
+ if (size >= kMaxEstimatedSize) break;
107
+ size_t childSize = estimateJsonSize(child);
108
+ size = safeAdd(size, childSize);
109
+ }
110
+ return size;
111
+ }
112
+
82
113
  void appendNodeJson(std::string& output, const std::shared_ptr<InternalMarkdownNode>& node) {
83
114
  output.push_back('{');
84
115
 
@@ -175,10 +206,6 @@ std::string flattenNodeText(const std::shared_ptr<InternalMarkdownNode>& node) {
175
206
  case NodeType::MathInline:
176
207
  case NodeType::HtmlInline:
177
208
  return node->content.value_or("");
178
- case NodeType::CodeBlock:
179
- case NodeType::MathBlock:
180
- case NodeType::HtmlBlock:
181
- return trimCopy(node->content.value_or("")) + "\n\n";
182
209
  case NodeType::LineBreak:
183
210
  return "\n";
184
211
  case NodeType::SoftBreak:
@@ -201,6 +228,9 @@ std::string flattenNodeText(const std::shared_ptr<InternalMarkdownNode>& node) {
201
228
  case NodeType::Paragraph:
202
229
  case NodeType::Heading:
203
230
  case NodeType::Blockquote:
231
+ case NodeType::CodeBlock:
232
+ case NodeType::MathBlock:
233
+ case NodeType::HtmlBlock:
204
234
  return trimCopy(childrenText) + "\n\n";
205
235
  case NodeType::ListItem:
206
236
  case NodeType::TaskListItem:
@@ -219,10 +249,8 @@ std::string flattenNodeText(const std::shared_ptr<InternalMarkdownNode>& node) {
219
249
  } // namespace
220
250
 
221
251
  std::string HybridMarkdownParser::parse(const std::string& text) {
222
- InternalParserOptions opts;
223
- opts.gfm = true;
224
- opts.math = true;
225
-
252
+ InternalParserOptions opts{.gfm = true, .math = true, .html = false};
253
+
226
254
  auto ast = parser_->parse(text, opts);
227
255
  return nodeToJson(ast);
228
256
  }
@@ -231,15 +259,14 @@ std::string HybridMarkdownParser::parseWithOptions(const std::string& text, cons
231
259
  InternalParserOptions internalOpts;
232
260
  internalOpts.gfm = options.gfm.value_or(true);
233
261
  internalOpts.math = options.math.value_or(true);
262
+ internalOpts.html = options.html.value_or(false);
234
263
 
235
264
  auto ast = parser_->parse(text, internalOpts);
236
265
  return nodeToJson(ast);
237
266
  }
238
267
 
239
268
  std::string HybridMarkdownParser::extractPlainText(const std::string& text) {
240
- InternalParserOptions opts;
241
- opts.gfm = true;
242
- opts.math = true;
269
+ InternalParserOptions opts{.gfm = true, .math = true, .html = false};
243
270
 
244
271
  auto ast = parser_->parse(text, opts);
245
272
  return flattenNodeText(ast);
@@ -249,6 +276,7 @@ std::string HybridMarkdownParser::extractPlainTextWithOptions(const std::string&
249
276
  InternalParserOptions internalOpts;
250
277
  internalOpts.gfm = options.gfm.value_or(true);
251
278
  internalOpts.math = options.math.value_or(true);
279
+ internalOpts.html = options.html.value_or(false);
252
280
 
253
281
  auto ast = parser_->parse(text, internalOpts);
254
282
  return flattenNodeText(ast);
@@ -256,7 +284,7 @@ std::string HybridMarkdownParser::extractPlainTextWithOptions(const std::string&
256
284
 
257
285
  std::string HybridMarkdownParser::nodeToJson(const std::shared_ptr<InternalMarkdownNode>& node) {
258
286
  std::string json;
259
- json.reserve(1024);
287
+ json.reserve(estimateJsonSize(node));
260
288
  appendNodeJson(json, node);
261
289
  return json;
262
290
  }
@@ -17,10 +17,10 @@ public:
17
17
 
18
18
  ~HybridMarkdownParser() override = default;
19
19
 
20
- std::string parse(const std::string& text) override;
21
- std::string parseWithOptions(const std::string& text, const ParserOptions& options) override;
22
- std::string extractPlainText(const std::string& text) override;
23
- std::string extractPlainTextWithOptions(const std::string& text, const ParserOptions& options) override;
20
+ [[nodiscard]] std::string parse(const std::string& text) override;
21
+ [[nodiscard]] std::string parseWithOptions(const std::string& text, const ParserOptions& options) override;
22
+ [[nodiscard]] std::string extractPlainText(const std::string& text) override;
23
+ [[nodiscard]] std::string extractPlainTextWithOptions(const std::string& text, const ParserOptions& options) override;
24
24
 
25
25
  private:
26
26
  std::unique_ptr<::NitroMarkdown::MD4CParser> parser_;
@@ -0,0 +1,2 @@
1
+ // Implementation is provided by the platform-specific HybridObject (iOS: Swift, Android: Kotlin)
2
+ // via the Nitrogen-generated bridge. No C++ implementation is required here.