react-native-nitro-markdown 0.5.1 → 0.5.3

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 (116) hide show
  1. package/README.md +257 -682
  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/cpp/cpp-adapter.cpp +4 -1
  7. package/android/src/main/java/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSession.kt +61 -21
  8. package/android/src/main/java/com/nitromarkdown/NitroMarkdownPackage.kt +6 -18
  9. package/cpp/bindings/HybridMarkdownParser.cpp +38 -12
  10. package/cpp/bindings/HybridMarkdownParser.hpp +4 -4
  11. package/cpp/bindings/HybridMarkdownSession.cpp +2 -0
  12. package/cpp/core/MD4CParser.cpp +128 -85
  13. package/cpp/core/MarkdownSessionCore.cpp +2 -0
  14. package/ios/HybridMarkdownSession.swift +89 -46
  15. package/lib/commonjs/headless.js +33 -7
  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 +47 -10
  22. package/lib/commonjs/markdown.js.map +1 -1
  23. package/lib/commonjs/renderers/code.js +1 -1
  24. package/lib/commonjs/renderers/code.js.map +1 -1
  25. package/lib/commonjs/renderers/image.js +6 -1
  26. package/lib/commonjs/renderers/image.js.map +1 -1
  27. package/lib/commonjs/renderers/link.js +7 -2
  28. package/lib/commonjs/renderers/link.js.map +1 -1
  29. package/lib/commonjs/renderers/list.js +2 -0
  30. package/lib/commonjs/renderers/list.js.map +1 -1
  31. package/lib/commonjs/renderers/math.js +4 -2
  32. package/lib/commonjs/renderers/math.js.map +1 -1
  33. package/lib/commonjs/renderers/table/cell-content.js +1 -1
  34. package/lib/commonjs/renderers/table/cell-content.js.map +1 -1
  35. package/lib/commonjs/renderers/table/index.js +10 -2
  36. package/lib/commonjs/renderers/table/index.js.map +1 -1
  37. package/lib/commonjs/theme.js +7 -7
  38. package/lib/commonjs/theme.js.map +1 -1
  39. package/lib/commonjs/utils/code-highlight.js +24 -25
  40. package/lib/commonjs/utils/code-highlight.js.map +1 -1
  41. package/lib/module/headless.js +34 -6
  42. package/lib/module/headless.js.map +1 -1
  43. package/lib/module/index.js +1 -1
  44. package/lib/module/index.js.map +1 -1
  45. package/lib/module/markdown-stream.js +1 -1
  46. package/lib/module/markdown-stream.js.map +1 -1
  47. package/lib/module/markdown.js +48 -11
  48. package/lib/module/markdown.js.map +1 -1
  49. package/lib/module/renderers/code.js +1 -1
  50. package/lib/module/renderers/code.js.map +1 -1
  51. package/lib/module/renderers/image.js +6 -1
  52. package/lib/module/renderers/image.js.map +1 -1
  53. package/lib/module/renderers/link.js +7 -2
  54. package/lib/module/renderers/link.js.map +1 -1
  55. package/lib/module/renderers/list.js +2 -0
  56. package/lib/module/renderers/list.js.map +1 -1
  57. package/lib/module/renderers/math.js +4 -2
  58. package/lib/module/renderers/math.js.map +1 -1
  59. package/lib/module/renderers/table/cell-content.js +1 -1
  60. package/lib/module/renderers/table/cell-content.js.map +1 -1
  61. package/lib/module/renderers/table/index.js +10 -2
  62. package/lib/module/renderers/table/index.js.map +1 -1
  63. package/lib/module/theme.js +7 -7
  64. package/lib/module/theme.js.map +1 -1
  65. package/lib/module/utils/code-highlight.js +24 -25
  66. package/lib/module/utils/code-highlight.js.map +1 -1
  67. package/lib/typescript/commonjs/headless.d.ts +9 -1
  68. package/lib/typescript/commonjs/headless.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/index.d.ts +3 -2
  70. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
  72. package/lib/typescript/commonjs/markdown.d.ts +7 -2
  73. package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
  74. package/lib/typescript/commonjs/renderers/code.d.ts.map +1 -1
  75. package/lib/typescript/commonjs/renderers/image.d.ts.map +1 -1
  76. package/lib/typescript/commonjs/renderers/link.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/renderers/list.d.ts.map +1 -1
  78. package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/renderers/table/cell-content.d.ts +4 -3
  80. package/lib/typescript/commonjs/renderers/table/cell-content.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/renderers/table/index.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/theme.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/utils/code-highlight.d.ts +1 -1
  84. package/lib/typescript/commonjs/utils/code-highlight.d.ts.map +1 -1
  85. package/lib/typescript/module/headless.d.ts +9 -1
  86. package/lib/typescript/module/headless.d.ts.map +1 -1
  87. package/lib/typescript/module/index.d.ts +3 -2
  88. package/lib/typescript/module/index.d.ts.map +1 -1
  89. package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
  90. package/lib/typescript/module/markdown.d.ts +7 -2
  91. package/lib/typescript/module/markdown.d.ts.map +1 -1
  92. package/lib/typescript/module/renderers/code.d.ts.map +1 -1
  93. package/lib/typescript/module/renderers/image.d.ts.map +1 -1
  94. package/lib/typescript/module/renderers/link.d.ts.map +1 -1
  95. package/lib/typescript/module/renderers/list.d.ts.map +1 -1
  96. package/lib/typescript/module/renderers/math.d.ts.map +1 -1
  97. package/lib/typescript/module/renderers/table/cell-content.d.ts +4 -3
  98. package/lib/typescript/module/renderers/table/cell-content.d.ts.map +1 -1
  99. package/lib/typescript/module/renderers/table/index.d.ts.map +1 -1
  100. package/lib/typescript/module/theme.d.ts.map +1 -1
  101. package/lib/typescript/module/utils/code-highlight.d.ts +1 -1
  102. package/lib/typescript/module/utils/code-highlight.d.ts.map +1 -1
  103. package/package.json +5 -3
  104. package/src/headless.ts +57 -7
  105. package/src/index.ts +16 -2
  106. package/src/markdown-stream.tsx +1 -0
  107. package/src/markdown.tsx +98 -31
  108. package/src/renderers/code.tsx +23 -16
  109. package/src/renderers/image.tsx +9 -1
  110. package/src/renderers/link.tsx +8 -2
  111. package/src/renderers/list.tsx +2 -0
  112. package/src/renderers/math.tsx +6 -2
  113. package/src/renderers/table/cell-content.tsx +15 -4
  114. package/src/renderers/table/index.tsx +15 -3
  115. package/src/theme.ts +34 -14
  116. 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)
@@ -38,3 +38,10 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE
38
38
  # Include Nitro autolinking (adds nitrogen sources, definitions, and links)
39
39
  include(${CMAKE_CURRENT_SOURCE_DIR}/../nitrogen/generated/android/NitroMarkdown+autolinking.cmake)
40
40
 
41
+ # L5: Per-build-type optimization flags. Release builds get full optimization and strip debug
42
+ # symbols from the binary. Debug builds disable optimization for easier debugging.
43
+ target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE
44
+ $<$<CONFIG:Release>:-O3 -DNDEBUG>
45
+ $<$<CONFIG:Debug>:-O0 -g>
46
+ )
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,7 +1,10 @@
1
1
  #include <jni.h>
2
+ #include <fbjni/fbjni.h>
2
3
  #include "NitroMarkdownOnLoad.hpp"
3
4
 
4
5
  JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
5
- return margelo::nitro::Markdown::initialize(vm);
6
+ return facebook::jni::initialize(vm, []() {
7
+ margelo::nitro::Markdown::registerAllNatives();
8
+ });
6
9
  }
7
10
 
@@ -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,38 +62,37 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
50
62
  }
51
63
 
52
64
  override fun getTextRange(from: Double, to: Double): String {
65
+ if (from.isNaN() || to.isNaN() || from < 0.0) 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 >= 0.0 && to >= from) { "Invalid range: from=$from must be >= 0 and to=$to must be >= from" }
81
92
  val newLength: Double
82
93
  synchronized(lock) {
83
- val start = from.toInt().coerceIn(0, buffer.length)
84
- val end = to.toInt().coerceIn(start, buffer.length)
94
+ val start = from.toLong().coerceIn(0L, buffer.length.toLong()).toInt()
95
+ val end = to.toLong().coerceIn(start.toLong(), buffer.length.toLong()).toInt()
85
96
  buffer.replace(start, end, text)
86
97
  newLength = buffer.length.toDouble()
87
98
  }
@@ -90,10 +101,39 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
90
101
  }
91
102
 
92
103
  private fun notifyListeners(from: Double, to: Double) {
93
- val currentListeners: Collection<(Double, Double) -> Unit>
104
+ val snapshot: List<(Double, Double) -> Unit>
94
105
  synchronized(lock) {
95
- currentListeners = listeners.values.toList()
106
+ snapshot = listeners.values.toList()
96
107
  }
97
- currentListeners.forEach { it(from, to) }
108
+ for (listener in snapshot) {
109
+ try {
110
+ listener(from, to)
111
+ } catch (e: Throwable) {
112
+ android.util.Log.e(TAG, "Listener callback threw an exception", e)
113
+ }
114
+ }
115
+ }
116
+
117
+ fun onDestroyed() {
118
+ synchronized(lock) {
119
+ isDestroyed = true
120
+ listeners.clear()
121
+ }
122
+ }
123
+
124
+ override fun dispose() {
125
+ onDestroyed()
126
+ super.dispose()
127
+ }
128
+
129
+ // H6: The C++ generated file (JHybridMarkdownSessionSpec.cpp) is marked DO NOT MODIFY and
130
+ // cannot be edited. However, fbjni (used by Nitro) automatically propagates Java/Kotlin
131
+ // exceptions thrown across the JNI boundary as C++ exceptions. Therefore, an
132
+ // IllegalArgumentException thrown in append() will be rethrown on the C++ side without
133
+ // requiring manual JNI exception checks in the generated code.
134
+
135
+ companion object {
136
+ private const val TAG = "HybridMarkdownSession"
137
+ private const val MAX_BUFFER_SIZE = 10 * 1024 * 1024 // 10 MB
98
138
  }
99
139
  }
@@ -1,28 +1,16 @@
1
1
  package com.nitromarkdown
2
2
 
3
- import com.facebook.react.TurboReactPackage
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 : TurboReactPackage() {
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};
253
+
226
254
  auto ast = parser_->parse(text, opts);
227
255
  return nodeToJson(ast);
228
256
  }
@@ -237,9 +265,7 @@ std::string HybridMarkdownParser::parseWithOptions(const std::string& text, cons
237
265
  }
238
266
 
239
267
  std::string HybridMarkdownParser::extractPlainText(const std::string& text) {
240
- InternalParserOptions opts;
241
- opts.gfm = true;
242
- opts.math = true;
268
+ InternalParserOptions opts{.gfm = true, .math = true};
243
269
 
244
270
  auto ast = parser_->parse(text, opts);
245
271
  return flattenNodeText(ast);
@@ -256,7 +282,7 @@ std::string HybridMarkdownParser::extractPlainTextWithOptions(const std::string&
256
282
 
257
283
  std::string HybridMarkdownParser::nodeToJson(const std::shared_ptr<InternalMarkdownNode>& node) {
258
284
  std::string json;
259
- json.reserve(1024);
285
+ json.reserve(estimateJsonSize(node));
260
286
  appendNodeJson(json, node);
261
287
  return json;
262
288
  }
@@ -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.