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
@@ -15,6 +15,18 @@ size_t clampInputSize(size_t inputSize) {
15
15
  }
16
16
  return inputSize;
17
17
  }
18
+
19
+ // Safe pointer offset calculation — guards against out-of-allocation arithmetic.
20
+ // md4c callbacks receive pointers into the input buffer, so arithmetic is valid
21
+ // as long as the input string is stable. This check catches any edge cases.
22
+ static MD_OFFSET safeOffset(const char* text, const char* base, size_t baseSize) noexcept {
23
+ if (text < base) return 0;
24
+ ptrdiff_t diff = text - base;
25
+ if (diff < 0 || static_cast<size_t>(diff) > baseSize) return 0;
26
+ // Check MD_OFFSET won't truncate
27
+ if (static_cast<size_t>(diff) > static_cast<size_t>(std::numeric_limits<MD_OFFSET>::max())) return 0;
28
+ return static_cast<MD_OFFSET>(diff);
29
+ }
18
30
  } // namespace
19
31
 
20
32
  class MD4CParser::Impl {
@@ -38,13 +50,21 @@ public:
38
50
  }
39
51
 
40
52
  void flushText() {
41
- if (!currentText.empty() && !nodeStack.empty()) {
42
- auto textNode = std::make_shared<MarkdownNode>(NodeType::Text);
43
- textNode->content = std::move(currentText);
44
- textNode->beg = currentTextBeg;
45
- textNode->end = lastTextEnd;
46
- nodeStack.top()->addChild(std::move(textNode));
47
- currentText.clear();
53
+ if (!currentText.empty()) {
54
+ if (!nodeStack.empty()) {
55
+ auto textNode = std::make_shared<MarkdownNode>(NodeType::Text);
56
+ textNode->content = std::move(currentText);
57
+ textNode->beg = currentTextBeg;
58
+ textNode->end = lastTextEnd;
59
+ nodeStack.top()->addChild(std::move(textNode));
60
+ currentText.clear();
61
+ } else {
62
+ #if defined(NITROMARKDOWN_DEBUG) || defined(DEBUG)
63
+ // This indicates a parser state bug - text available but no node to attach it to
64
+ fprintf(stderr, "[NitroMarkdown] Warning: flushText called with empty nodeStack, text dropped: %.50s\n", currentText.c_str());
65
+ #endif
66
+ currentText.clear();
67
+ }
48
68
  }
49
69
  }
50
70
 
@@ -74,17 +94,18 @@ public:
74
94
  std::string result;
75
95
  result.reserve(attr->size);
76
96
 
77
- for (unsigned i = 0; ; i++) {
97
+ // md4c invariant: substr_types is terminated by an entry where
98
+ // substr_offsets[i] == attr->size (the sentinel entry). Reading
99
+ // substr_offsets[i+1] is always valid when substr_offsets[i] < attr->size.
100
+ for (unsigned i = 0; attr->substr_offsets[i] < attr->size; i++) {
78
101
  size_t start = static_cast<size_t>(attr->substr_offsets[i]);
79
- size_t end = static_cast<size_t>(attr->substr_offsets[i + 1]);
102
+ size_t end = static_cast<size_t>(attr->substr_offsets[i + 1]); // safe: [i+1] always valid when [i] < size
80
103
 
81
- if (end > attr->size) {
104
+ if (end > static_cast<size_t>(attr->size)) {
82
105
  end = static_cast<size_t>(attr->size);
83
106
  }
84
- if (start > end) {
85
- break;
86
- }
87
107
 
108
+ // Append content for all recognised text types
88
109
  if (attr->substr_types[i] == MD_TEXT_NORMAL ||
89
110
  attr->substr_types[i] == MD_TEXT_ENTITY ||
90
111
  attr->substr_types[i] == MD_TEXT_NULLCHAR) {
@@ -92,12 +113,11 @@ public:
92
113
  result.append(attr->text + start, end - start);
93
114
  }
94
115
  }
95
-
96
- if (end >= attr->size) {
97
- break;
98
- }
99
116
  }
100
117
 
118
+ // Fallback: if all substrings had unrecognised types (should not occur
119
+ // per the md4c spec, but guards against future spec extensions), return
120
+ // the raw attribute text.
101
121
  if (result.empty() && attr->size > 0) {
102
122
  result.assign(attr->text, attr->size);
103
123
  }
@@ -105,25 +125,27 @@ public:
105
125
  return result;
106
126
  }
107
127
 
108
- static int enterBlock(MD_BLOCKTYPE type, void* detail, MD_OFFSET off, void* userdata) {
128
+ static int enterBlock(MD_BLOCKTYPE type, void* detail, MD_OFFSET off, void* userdata) noexcept {
129
+ try {
109
130
  auto* impl = static_cast<Impl*>(userdata);
110
-
131
+ if (impl == nullptr) return 1; // Signal error to md4c
132
+
111
133
  switch (type) {
112
134
  case MD_BLOCK_DOC:
113
135
  break;
114
-
136
+
115
137
  case MD_BLOCK_QUOTE: {
116
138
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Blockquote), off);
117
139
  break;
118
140
  }
119
-
141
+
120
142
  case MD_BLOCK_UL: {
121
143
  auto node = std::make_shared<MarkdownNode>(NodeType::List);
122
144
  node->ordered = false;
123
145
  impl->pushNode(node, off);
124
146
  break;
125
147
  }
126
-
148
+
127
149
  case MD_BLOCK_OL: {
128
150
  auto* d = static_cast<MD_BLOCK_OL_DETAIL*>(detail);
129
151
  auto node = std::make_shared<MarkdownNode>(NodeType::List);
@@ -132,7 +154,7 @@ public:
132
154
  impl->pushNode(node, off);
133
155
  break;
134
156
  }
135
-
157
+
136
158
  case MD_BLOCK_LI: {
137
159
  auto* d = static_cast<MD_BLOCK_LI_DETAIL*>(detail);
138
160
  if (d->is_task) {
@@ -144,12 +166,12 @@ public:
144
166
  }
145
167
  break;
146
168
  }
147
-
169
+
148
170
  case MD_BLOCK_HR: {
149
171
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::HorizontalRule), off);
150
172
  break;
151
173
  }
152
-
174
+
153
175
  case MD_BLOCK_H: {
154
176
  auto* d = static_cast<MD_BLOCK_H_DETAIL*>(detail);
155
177
  auto node = std::make_shared<MarkdownNode>(NodeType::Heading);
@@ -157,7 +179,7 @@ public:
157
179
  impl->pushNode(node, off);
158
180
  break;
159
181
  }
160
-
182
+
161
183
  case MD_BLOCK_CODE: {
162
184
  auto* d = static_cast<MD_BLOCK_CODE_DETAIL*>(detail);
163
185
  auto node = std::make_shared<MarkdownNode>(NodeType::CodeBlock);
@@ -167,37 +189,37 @@ public:
167
189
  impl->pushNode(node, off);
168
190
  break;
169
191
  }
170
-
192
+
171
193
  case MD_BLOCK_HTML: {
172
194
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::HtmlBlock), off);
173
195
  break;
174
196
  }
175
-
197
+
176
198
  case MD_BLOCK_P: {
177
199
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Paragraph), off);
178
200
  break;
179
201
  }
180
-
202
+
181
203
  case MD_BLOCK_TABLE: {
182
204
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Table), off);
183
205
  break;
184
206
  }
185
-
207
+
186
208
  case MD_BLOCK_THEAD: {
187
209
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::TableHead), off);
188
210
  break;
189
211
  }
190
-
212
+
191
213
  case MD_BLOCK_TBODY: {
192
214
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::TableBody), off);
193
215
  break;
194
216
  }
195
-
217
+
196
218
  case MD_BLOCK_TR: {
197
219
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::TableRow), off);
198
220
  break;
199
221
  }
200
-
222
+
201
223
  case MD_BLOCK_TH: {
202
224
  auto* d = static_cast<MD_BLOCK_TD_DETAIL*>(detail);
203
225
  auto node = std::make_shared<MarkdownNode>(NodeType::TableCell);
@@ -211,7 +233,7 @@ public:
211
233
  impl->pushNode(node, off);
212
234
  break;
213
235
  }
214
-
236
+
215
237
  case MD_BLOCK_TD: {
216
238
  auto* d = static_cast<MD_BLOCK_TD_DETAIL*>(detail);
217
239
  auto node = std::make_shared<MarkdownNode>(NodeType::TableCell);
@@ -226,14 +248,18 @@ public:
226
248
  break;
227
249
  }
228
250
  }
229
-
251
+
230
252
  return 0;
253
+ } catch (...) {
254
+ return 1; // Signal error to md4c
255
+ }
231
256
  }
232
257
 
233
- static int leaveBlock(MD_BLOCKTYPE type, void* detail, MD_OFFSET off, void* userdata) {
234
- (void)detail;
258
+ static int leaveBlock(MD_BLOCKTYPE type, [[maybe_unused]] void* detail, MD_OFFSET off, void* userdata) noexcept {
259
+ try {
235
260
  auto* impl = static_cast<Impl*>(userdata);
236
-
261
+ if (impl == nullptr) return 1; // Signal error to md4c
262
+
237
263
  switch (type) {
238
264
  case MD_BLOCK_DOC:
239
265
  impl->root->end = off;
@@ -245,29 +271,34 @@ public:
245
271
  impl->popNode(off);
246
272
  break;
247
273
  }
248
-
274
+
249
275
  return 0;
276
+ } catch (...) {
277
+ return 1; // Signal error to md4c
278
+ }
250
279
  }
251
280
 
252
- static int enterSpan(MD_SPANTYPE type, void* detail, MD_OFFSET off, void* userdata) {
281
+ static int enterSpan(MD_SPANTYPE type, void* detail, MD_OFFSET off, void* userdata) noexcept {
282
+ try {
253
283
  auto* impl = static_cast<Impl*>(userdata);
254
-
284
+ if (impl == nullptr) return 1; // Signal error to md4c
285
+
255
286
  switch (type) {
256
287
  case MD_SPAN_EM: {
257
288
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Italic), off);
258
289
  break;
259
290
  }
260
-
291
+
261
292
  case MD_SPAN_STRONG: {
262
293
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Bold), off);
263
294
  break;
264
295
  }
265
-
296
+
266
297
  case MD_SPAN_DEL: {
267
298
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Strikethrough), off);
268
299
  break;
269
300
  }
270
-
301
+
271
302
  case MD_SPAN_A: {
272
303
  auto* d = static_cast<MD_SPAN_A_DETAIL*>(detail);
273
304
  auto node = std::make_shared<MarkdownNode>(NodeType::Link);
@@ -280,7 +311,7 @@ public:
280
311
  impl->pushNode(node, off);
281
312
  break;
282
313
  }
283
-
314
+
284
315
  case MD_SPAN_IMG: {
285
316
  auto* d = static_cast<MD_SPAN_IMG_DETAIL*>(detail);
286
317
  auto node = std::make_shared<MarkdownNode>(NodeType::Image);
@@ -293,40 +324,44 @@ public:
293
324
  impl->pushNode(node, off);
294
325
  break;
295
326
  }
296
-
327
+
297
328
  case MD_SPAN_CODE: {
298
329
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::CodeInline), off);
299
330
  break;
300
331
  }
301
-
332
+
302
333
  case MD_SPAN_LATEXMATH: {
303
334
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::MathInline), off);
304
335
  break;
305
336
  }
306
-
337
+
307
338
  case MD_SPAN_LATEXMATH_DISPLAY: {
308
339
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::MathBlock), off);
309
340
  break;
310
341
  }
311
-
342
+
312
343
  case MD_SPAN_U: {
313
344
  impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Italic), off);
314
345
  break;
315
346
  }
316
-
347
+
317
348
  case MD_SPAN_WIKILINK: {
318
349
  auto node = std::make_shared<MarkdownNode>(NodeType::Link);
319
350
  impl->pushNode(node, off);
320
351
  break;
321
352
  }
322
353
  }
323
-
354
+
324
355
  return 0;
356
+ } catch (...) {
357
+ return 1; // Signal error to md4c
358
+ }
325
359
  }
326
360
 
327
- static int leaveSpan(MD_SPANTYPE type, void* detail, MD_OFFSET off, void* userdata) {
328
- (void)detail;
361
+ static int leaveSpan(MD_SPANTYPE type, [[maybe_unused]] void* detail, MD_OFFSET off, void* userdata) noexcept {
362
+ try {
329
363
  auto* impl = static_cast<Impl*>(userdata);
364
+ if (impl == nullptr) return 1; // Signal error to md4c
330
365
 
331
366
  if (!impl->nodeStack.empty()) {
332
367
  auto currentNode = impl->nodeStack.top();
@@ -349,71 +384,89 @@ public:
349
384
 
350
385
  impl->popNode(off);
351
386
  return 0;
387
+ } catch (...) {
388
+ return 1; // Signal error to md4c
389
+ }
352
390
  }
353
391
 
354
- static int text(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata) {
392
+ static int text(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata) noexcept {
393
+ try {
355
394
  auto* impl = static_cast<Impl*>(userdata);
395
+ if (impl == nullptr) return 1; // Signal error to md4c
356
396
 
357
397
  if (!text || size == 0) return 0;
358
398
 
359
399
  switch (type) {
360
400
  case MD_TEXT_NULLCHAR: {
361
- MD_OFFSET off = impl->lastTextEnd;
362
- ptrdiff_t diff = text - impl->inputText;
363
- if (diff >= 0 && static_cast<size_t>(diff) <= impl->inputTextSize) {
364
- off = static_cast<MD_OFFSET>(diff);
365
- }
401
+ MD_OFFSET off = safeOffset(text, impl->inputText, impl->inputTextSize);
402
+ if (off == 0 && text != impl->inputText) off = impl->lastTextEnd;
366
403
  if (impl->currentText.empty()) impl->currentTextBeg = off;
367
404
  impl->currentText += '\0';
368
405
  impl->lastTextEnd = off + 1;
369
406
  break;
370
407
  }
371
-
408
+
372
409
  case MD_TEXT_BR:
373
410
  impl->flushText();
374
- impl->nodeStack.top()->addChild(
375
- std::make_shared<MarkdownNode>(NodeType::LineBreak));
411
+ if (!impl->nodeStack.empty()) {
412
+ impl->nodeStack.top()->addChild(
413
+ std::make_shared<MarkdownNode>(NodeType::LineBreak));
414
+ }
376
415
  break;
377
-
416
+
378
417
  case MD_TEXT_SOFTBR:
379
418
  impl->flushText();
380
- impl->nodeStack.top()->addChild(
381
- std::make_shared<MarkdownNode>(NodeType::SoftBreak));
419
+ if (!impl->nodeStack.empty()) {
420
+ impl->nodeStack.top()->addChild(
421
+ std::make_shared<MarkdownNode>(NodeType::SoftBreak));
422
+ }
382
423
  break;
383
-
424
+
384
425
  case MD_TEXT_HTML:
385
426
  impl->flushText();
386
- {
427
+ if (!impl->nodeStack.empty() && text && size > 0) {
428
+ MD_OFFSET off = safeOffset(text, impl->inputText, impl->inputTextSize);
429
+ if (off == 0 && text != impl->inputText) off = impl->lastTextEnd;
430
+
431
+ if (impl->nodeStack.top()->type == NodeType::HtmlBlock) {
432
+ auto htmlBlock = impl->nodeStack.top();
433
+ if (htmlBlock->content.has_value()) {
434
+ htmlBlock->content->append(text, size);
435
+ } else {
436
+ htmlBlock->content = std::string(text, size);
437
+ }
438
+ htmlBlock->end = off + size;
439
+ impl->lastTextEnd = off + size;
440
+ break;
441
+ }
442
+
387
443
  auto node = std::make_shared<MarkdownNode>(NodeType::HtmlInline);
388
444
  node->content = std::string(text, size);
445
+ node->beg = off;
446
+ node->end = off + size;
389
447
  impl->nodeStack.top()->addChild(node);
448
+ impl->lastTextEnd = off + size;
390
449
  }
391
450
  break;
392
-
451
+
393
452
  case MD_TEXT_ENTITY:
394
453
  if (text && size > 0) {
395
- MD_OFFSET off = impl->lastTextEnd;
396
- ptrdiff_t diff = text - impl->inputText;
397
- if (diff >= 0 && static_cast<size_t>(diff) <= impl->inputTextSize) {
398
- off = static_cast<MD_OFFSET>(diff);
399
- }
454
+ MD_OFFSET off = safeOffset(text, impl->inputText, impl->inputTextSize);
455
+ if (off == 0 && text != impl->inputText) off = impl->lastTextEnd;
400
456
  if (impl->currentText.empty()) impl->currentTextBeg = off;
401
457
  impl->currentText.append(text, size);
402
458
  impl->lastTextEnd = off + size;
403
459
  }
404
460
  break;
405
-
461
+
406
462
  case MD_TEXT_NORMAL:
407
463
  case MD_TEXT_CODE:
408
464
  case MD_TEXT_LATEXMATH:
409
465
  default: {
410
466
  if (text && size > 0) {
411
- MD_OFFSET off = impl->lastTextEnd;
412
- ptrdiff_t diff = text - impl->inputText;
413
- if (diff >= 0 && static_cast<size_t>(diff) <= impl->inputTextSize) {
414
- off = static_cast<MD_OFFSET>(diff);
415
- }
416
-
467
+ MD_OFFSET off = safeOffset(text, impl->inputText, impl->inputTextSize);
468
+ if (off == 0 && text != impl->inputText) off = impl->lastTextEnd;
469
+
417
470
  if (impl->currentText.empty()) {
418
471
  impl->currentTextBeg = off;
419
472
  }
@@ -423,8 +476,11 @@ public:
423
476
  break;
424
477
  }
425
478
  }
426
-
479
+
427
480
  return 0;
481
+ } catch (...) {
482
+ return 1; // Signal error to md4c
483
+ }
428
484
  }
429
485
  };
430
486
 
@@ -438,7 +494,7 @@ std::shared_ptr<MarkdownNode> MD4CParser::parse(const std::string& markdown, con
438
494
  size_t inputSize = clampInputSize(markdown.size());
439
495
  impl_->inputTextSize = inputSize;
440
496
 
441
- unsigned int flags = MD_FLAG_NOHTML;
497
+ unsigned int flags = options.html ? 0 : MD_FLAG_NOHTML;
442
498
 
443
499
  if (options.gfm) {
444
500
  flags |= MD_FLAG_TABLES;
@@ -463,10 +519,15 @@ std::shared_ptr<MarkdownNode> MD4CParser::parse(const std::string& markdown, con
463
519
  nullptr
464
520
  };
465
521
 
466
- md_parse(markdown.c_str(),
467
- static_cast<MD_SIZE>(inputSize),
468
- &parser,
469
- impl_.get());
522
+ int result = md_parse(markdown.c_str(),
523
+ static_cast<MD_SIZE>(inputSize),
524
+ &parser,
525
+ impl_.get());
526
+ if (result != 0) {
527
+ // md_parse failed (callback aborted or runtime error).
528
+ // The AST may be partial but is still a valid tree rooted at Document,
529
+ // so we continue rather than throw — callers can use whatever was parsed.
530
+ }
470
531
 
471
532
  impl_->flushText();
472
533
  return impl_->root;
@@ -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.
@@ -118,7 +118,7 @@ struct MarkdownNode {
118
118
  struct ParserOptions {
119
119
  bool gfm = true;
120
120
  bool math = true;
121
+ bool html = false;
121
122
  };
122
123
 
123
124
  } // namespace NitroMarkdown
124
-