apex-ruby 1.0.18 → 1.0.20

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 (207) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -3
  3. data/ext/apex_ext/apex_ext.c +260 -91
  4. data/ext/apex_ext/apex_src/CHANGELOG.md +440 -0
  5. data/ext/apex_ext/apex_src/CMakeLists.txt +15 -1
  6. data/ext/apex_ext/apex_src/Formula/apex.rb +7 -2
  7. data/ext/apex_ext/apex_src/Package.swift +15 -4
  8. data/ext/apex_ext/apex_src/README.md +94 -35
  9. data/ext/apex_ext/apex_src/VERSION +1 -1
  10. data/ext/apex_ext/apex_src/apex-debug.log +4 -0
  11. data/ext/apex_ext/apex_src/apex.code-workspace +38 -0
  12. data/ext/apex_ext/apex_src/apex.taskpaper +12 -0
  13. data/ext/apex_ext/apex_src/apex_deflist_debug.log +25 -0
  14. data/ext/apex_ext/apex_src/buildnotes.md +1435 -0
  15. data/ext/apex_ext/apex_src/cli/main.c +262 -33
  16. data/ext/apex_ext/apex_src/debug.log +7 -0
  17. data/ext/apex_ext/apex_src/docs/CITATIONS_EXAMPLES.md +274 -0
  18. data/ext/apex_ext/apex_src/docs/CITATIONS_IMPLEMENTATION_PLAN.md +749 -0
  19. data/ext/apex_ext/apex_src/docs/CITATIONS_PLAN.md +744 -0
  20. data/ext/apex_ext/apex_src/docs/INDEX_IMPLEMENTATION_ANALYSIS.md +382 -0
  21. data/ext/apex_ext/apex_src/docs/plans/2025-02-27-man-page-output-implementation.md +351 -0
  22. data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-12-toc-output-format.md +745 -0
  23. data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-18-bear-image-attributes.md +829 -0
  24. data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-19-apex-node-npm.md +1148 -0
  25. data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-12-toc-output-format-design.md +161 -0
  26. data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-18-bear-image-attributes-design.md +184 -0
  27. data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-19-apex-node-npm-design.md +211 -0
  28. data/ext/apex_ext/apex_src/documentation/app-settings-table.md +67 -0
  29. data/ext/apex_ext/apex_src/documentation/app-transformed/Citations.md +340 -0
  30. data/ext/apex_ext/apex_src/documentation/app-transformed/Credits.md +11 -0
  31. data/ext/apex_ext/apex_src/documentation/app-transformed/Header-IDs.md +234 -0
  32. data/ext/apex_ext/apex_src/documentation/app-transformed/Inline-Attribute-Lists.md +516 -0
  33. data/ext/apex_ext/apex_src/documentation/app-transformed/Metadata-Transforms.md +461 -0
  34. data/ext/apex_ext/apex_src/documentation/app-transformed/Modes.md +193 -0
  35. data/ext/apex_ext/apex_src/documentation/app-transformed/Multi-File-Documents.md +201 -0
  36. data/ext/apex_ext/apex_src/documentation/app-transformed/Plugins.md +678 -0
  37. data/ext/apex_ext/apex_src/documentation/app-transformed/Syntax.md +1367 -0
  38. data/ext/apex_ext/apex_src/documentation/docsets/Apex Command Line Options.cheatsheet.txt +6 -0
  39. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/C-API.html +774 -300
  40. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Callouts.html +1194 -0
  41. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Citations.html +416 -216
  42. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Command-Line-Options.html +3544 -1094
  43. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Configuration.html +371 -97
  44. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Credits.html +153 -23
  45. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Examples.html +358 -124
  46. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Filters.html +1656 -0
  47. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Generating-Man-Pages.html +1226 -0
  48. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Getting-Started.html +201 -56
  49. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Header-IDs.html +279 -109
  50. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Home.html +200 -53
  51. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Indices.html +2327 -0
  52. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Inline-Attribute-Lists.html +690 -271
  53. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Installation.html +201 -55
  54. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Limitations-and-Roadmap.html +238 -68
  55. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Metadata-Transforms.html +421 -221
  56. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Modes.html +340 -78
  57. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-File-Documents.html +251 -86
  58. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-Format-Images.html +1362 -0
  59. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Pandoc-Integration.html +277 -90
  60. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Plugins.html +673 -346
  61. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Quarto-Mode.html +1405 -0
  62. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Rendering-Markdown-In-Terminal.html +1902 -0
  63. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Syntax.html +1939 -1698
  64. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Tables.html +1781 -0
  65. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Troubleshooting.html +315 -140
  66. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Usage.html +307 -113
  67. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Using-Apex-with-Jekyll.html +1132 -0
  68. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Writing-Tests.html +1754 -0
  69. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Xcode-Integration.html +872 -376
  70. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/docSet.dsidx +0 -0
  71. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/optimizedIndex.dsidx +0 -0
  72. data/ext/apex_ext/apex_src/documentation/html/apex-app-docs.html +8746 -0
  73. data/ext/apex_ext/apex_src/documentation/shared_styles.css +20 -15
  74. data/ext/apex_ext/apex_src/documentation/transform_for_app.md +55 -0
  75. data/ext/apex_ext/apex_src/escaping-repro.md +103 -0
  76. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/Info.plist +8 -0
  77. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/QuickLook/Preview.png +0 -0
  78. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/document.wflow +547 -0
  79. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown-to-RTF.workflow.zip +0 -0
  80. data/ext/apex_ext/apex_src/examples/quick-actions/README.md +33 -0
  81. data/ext/apex_ext/apex_src/include/apex/apex.h +92 -6
  82. data/ext/apex_ext/apex_src/include/apex/ast_rtf.h +23 -0
  83. data/ext/apex_ext/apex_src/include/apex/ast_terminal.h +6 -0
  84. data/ext/apex_ext/apex_src/include/apex/module.modulemap +5 -0
  85. data/ext/apex_ext/apex_src/include/apex/plugins.h +105 -0
  86. data/ext/apex_ext/apex_src/include/apexc.h +1 -0
  87. data/ext/apex_ext/apex_src/man/apex.1 +82 -78
  88. data/ext/apex_ext/apex_src/man/apex.1.md +81 -4
  89. data/ext/apex_ext/apex_src/objc/Apex.swift +73 -0
  90. data/ext/apex_ext/apex_src/objc/ApexPlugins.swift +79 -0
  91. data/ext/apex_ext/apex_src/objc/NSString+Apex.h +83 -5
  92. data/ext/apex_ext/apex_src/objc/NSString+Apex.m +244 -1
  93. data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.h +76 -0
  94. data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.m +169 -0
  95. data/ext/apex_ext/apex_src/output.html +87 -0
  96. data/ext/apex_ext/apex_src/plans/quarto-mode.md +442 -0
  97. data/ext/apex_ext/apex_src/src/_README.md +85 -32
  98. data/ext/apex_ext/apex_src/src/apex.c +770 -200
  99. data/ext/apex_ext/apex_src/src/ast_rtf.c +848 -0
  100. data/ext/apex_ext/apex_src/src/ast_terminal.c +184 -7
  101. data/ext/apex_ext/apex_src/src/extensions/abbreviations.c +10 -3
  102. data/ext/apex_ext/apex_src/src/extensions/advanced_tables.c +7 -11
  103. data/ext/apex_ext/apex_src/src/extensions/bear_image_attrs.c +467 -0
  104. data/ext/apex_ext/apex_src/src/extensions/bear_image_attrs.h +27 -0
  105. data/ext/apex_ext/apex_src/src/extensions/callouts.c +400 -4
  106. data/ext/apex_ext/apex_src/src/extensions/callouts.h +15 -5
  107. data/ext/apex_ext/apex_src/src/extensions/citations.c +26 -5
  108. data/ext/apex_ext/apex_src/src/extensions/code_fence_attrs.c +632 -0
  109. data/ext/apex_ext/apex_src/src/extensions/code_fence_attrs.h +22 -0
  110. data/ext/apex_ext/apex_src/src/extensions/concordance.c +632 -0
  111. data/ext/apex_ext/apex_src/src/extensions/definition_list.c +129 -15
  112. data/ext/apex_ext/apex_src/src/extensions/grid_tables.c +1050 -121
  113. data/ext/apex_ext/apex_src/src/extensions/html_markdown.c +569 -16
  114. data/ext/apex_ext/apex_src/src/extensions/ial.c +502 -52
  115. data/ext/apex_ext/apex_src/src/extensions/ial.h +2 -1
  116. data/ext/apex_ext/apex_src/src/extensions/includes.c +532 -33
  117. data/ext/apex_ext/apex_src/src/extensions/includes.h +7 -1
  118. data/ext/apex_ext/apex_src/src/extensions/index.c +1427 -140
  119. data/ext/apex_ext/apex_src/src/extensions/index.h +28 -0
  120. data/ext/apex_ext/apex_src/src/extensions/metadata.c +200 -161
  121. data/ext/apex_ext/apex_src/src/extensions/quarto_diagrams.c +251 -0
  122. data/ext/apex_ext/apex_src/src/extensions/quarto_diagrams.h +22 -0
  123. data/ext/apex_ext/apex_src/src/extensions/quarto_lists.c +624 -0
  124. data/ext/apex_ext/apex_src/src/extensions/quarto_lists.h +35 -0
  125. data/ext/apex_ext/apex_src/src/extensions/quarto_polish.c +264 -0
  126. data/ext/apex_ext/apex_src/src/extensions/quarto_polish.h +23 -0
  127. data/ext/apex_ext/apex_src/src/extensions/quarto_shortcodes.c +307 -0
  128. data/ext/apex_ext/apex_src/src/extensions/quarto_shortcodes.h +21 -0
  129. data/ext/apex_ext/apex_src/src/extensions/raw_content.c +351 -0
  130. data/ext/apex_ext/apex_src/src/extensions/raw_content.h +20 -0
  131. data/ext/apex_ext/apex_src/src/extensions/relaxed_tables.c +117 -3
  132. data/ext/apex_ext/apex_src/src/extensions/special_markers.c +53 -0
  133. data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.c +23 -1
  134. data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.c +83 -92
  135. data/ext/apex_ext/apex_src/src/extensions/toc.c +446 -25
  136. data/ext/apex_ext/apex_src/src/extensions/toc.h +26 -1
  137. data/ext/apex_ext/apex_src/src/extensions/wiki_links.c +28 -0
  138. data/ext/apex_ext/apex_src/src/html_renderer.c +387 -17
  139. data/ext/apex_ext/apex_src/src/plugin_catalog.c +517 -0
  140. data/ext/apex_ext/apex_src/src/plugins_remote.c +88 -14
  141. data/ext/apex_ext/apex_src/src/plugins_remote.h +34 -0
  142. data/ext/apex_ext/apex_src/src/pretty_html.c +6 -1
  143. data/ext/apex_ext/apex_src/tests/fixtures/comprehensive_test.md +21 -0
  144. data/ext/apex_ext/apex_src/tests/fixtures/images/bear_image_attributes.md +10 -0
  145. data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.md +19 -0
  146. data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.txt +11 -0
  147. data/ext/apex_ext/apex_src/tests/fixtures/minimal_pb.md +2 -0
  148. data/ext/apex_ext/apex_src/tests/fixtures/obsidian-callouts.md +46 -0
  149. data/ext/apex_ext/apex_src/tests/fixtures/pagebreak_html_repro.md +5 -0
  150. data/ext/apex_ext/apex_src/tests/fixtures/python-callouts.md +47 -0
  151. data/ext/apex_ext/apex_src/tests/fixtures/quarto/callouts.md +3 -0
  152. data/ext/apex_ext/apex_src/tests/fixtures/quarto/code-attrs.md +3 -0
  153. data/ext/apex_ext/apex_src/tests/fixtures/quarto/diagrams.md +8 -0
  154. data/ext/apex_ext/apex_src/tests/fixtures/quarto/lists.md +19 -0
  155. data/ext/apex_ext/apex_src/tests/fixtures/quarto/raw-content.md +5 -0
  156. data/ext/apex_ext/apex_src/tests/fixtures/quarto/shortcodes.md +7 -0
  157. data/ext/apex_ext/apex_src/tests/fixtures/quarto/smoke.md +81 -0
  158. data/ext/apex_ext/apex_src/tests/fixtures/quarto/spans.md +5 -0
  159. data/ext/apex_ext/apex_src/tests/fixtures/quarto-callouts.md +47 -0
  160. data/ext/apex_ext/apex_src/tests/fixtures/speed.md +192 -0
  161. data/ext/apex_ext/apex_src/tests/fixtures/tables/grid-tables.md +37 -0
  162. data/ext/apex_ext/apex_src/tests/paginate_cli_test.sh +16 -0
  163. data/ext/apex_ext/apex_src/tests/test_bear_image_attrs.c +301 -0
  164. data/ext/apex_ext/apex_src/tests/test_concordance.tsv +4 -0
  165. data/ext/apex_ext/apex_src/tests/test_escaping_repro.c +82 -0
  166. data/ext/apex_ext/apex_src/tests/test_extensions.c +821 -1
  167. data/ext/apex_ext/apex_src/tests/test_ial.c +7 -0
  168. data/ext/apex_ext/apex_src/tests/test_marked_integration.c +25 -0
  169. data/ext/apex_ext/apex_src/tests/test_metadata.c +129 -0
  170. data/ext/apex_ext/apex_src/tests/test_output.c +568 -2
  171. data/ext/apex_ext/apex_src/tests/test_runner.c +11 -0
  172. data/ext/apex_ext/apex_src/tests/test_tables.c +420 -0
  173. data/ext/apex_ext/apex_src/vendor/cmark-gfm/CMakeLists.txt +1 -1
  174. data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/autolink.c +77 -1
  175. data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/table.c +135 -5
  176. data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/blocks.c +56 -1
  177. data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/config.h +38 -0
  178. data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/main.c +1 -1
  179. data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/cmark.cpython-314.pyc +0 -0
  180. data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/normalize.cpython-314.pyc +0 -0
  181. data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/extensions.txt +69 -0
  182. data/ext/apex_ext/apex_src/xcode_build/CMakeScripts/ReRunCMake.make +74 -0
  183. data/ext/apex_ext/apex_src/xcode_build/CTestTestfile.cmake +22 -0
  184. data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Apex +0 -0
  185. data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Resources/Info.plist +44 -0
  186. data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/_CodeSignature/CodeResources +128 -0
  187. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.pbxproj +5118 -0
  188. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  189. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  190. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/WorkspaceSettings.xcsettings +8 -0
  191. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/xcuserdata/ttscoff.xcuserdatad/xcschemes/xcschememanagement.plist +99 -0
  192. data/ext/apex_ext/apex_src/xcode_build/cmake_install.cmake +452 -0
  193. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/CMakeScripts/ReRunCMake.make +52 -0
  194. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmake_install.cmake +55 -0
  195. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.pbxproj +2455 -0
  196. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  197. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/extensions/cmake_install.cmake +171 -0
  198. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/man/cmake_install.cmake +55 -0
  199. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmake_install.cmake +220 -0
  200. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_export.h +43 -0
  201. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_version.h +7 -0
  202. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/config.h +76 -0
  203. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/libcmark-gfm.pc +10 -0
  204. data/ext/apex_ext/extconf.rb +35 -0
  205. data/lib/apex/version.rb +2 -2
  206. metadata +117 -2
  207. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/tempOptimizedIndex.dsidx +0 -0
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  #include "ial.h"
6
+ #include "bear_image_attrs.h"
6
7
  #include "table.h" /* For CMARK_NODE_TABLE */
7
8
  #include "apex/apex.h" /* For apex_mode_t */
8
9
  #include <string.h>
@@ -348,9 +349,13 @@ ald_entry *apex_extract_alds(char **text_ptr) {
348
349
  while ((line_end = strchr(line_start, '\n')) != NULL || *line_start) {
349
350
  if (!line_end) line_end = line_start + strlen(line_start);
350
351
 
351
- size_t line_len = line_end - line_start;
352
- char line[2048];
353
- if (line_len >= sizeof(line)) line_len = sizeof(line) - 1;
352
+ size_t line_len = (size_t)(line_end - line_start);
353
+ char *line = malloc(line_len + 1);
354
+ if (!line) {
355
+ apex_free_alds(alds);
356
+ free(output);
357
+ return NULL;
358
+ }
354
359
  memcpy(line, line_start, line_len);
355
360
  line[line_len] = '\0';
356
361
 
@@ -371,6 +376,7 @@ ald_entry *apex_extract_alds(char **text_ptr) {
371
376
  }
372
377
 
373
378
  /* Skip this line in output */
379
+ free(line);
374
380
  line_start = *line_end ? line_end + 1 : line_end;
375
381
  continue;
376
382
  }
@@ -378,6 +384,7 @@ ald_entry *apex_extract_alds(char **text_ptr) {
378
384
  /* Not an ALD, copy line to output */
379
385
  memcpy(output_write, line_start, line_len);
380
386
  output_write += line_len;
387
+ free(line);
381
388
  if (*line_end) {
382
389
  *output_write++ = '\n';
383
390
  line_start = line_end + 1;
@@ -477,6 +484,128 @@ static apex_attributes *merge_attributes(apex_attributes *base, apex_attributes
477
484
  return merged;
478
485
  }
479
486
 
487
+ static apex_attributes *copy_attributes(const apex_attributes *source) {
488
+ apex_attributes *copy = create_attributes();
489
+ if (!copy) return NULL;
490
+
491
+ if (source) {
492
+ if (source->id) copy->id = strdup(source->id);
493
+ for (int i = 0; i < source->class_count; i++) {
494
+ add_class(copy, source->classes[i]);
495
+ }
496
+ for (int i = 0; i < source->attr_count; i++) {
497
+ add_attribute(copy, source->keys[i], source->values[i]);
498
+ }
499
+ }
500
+
501
+ return copy;
502
+ }
503
+
504
+ static char *escape_bear_attribute_value(const char *value) {
505
+ size_t length = 0;
506
+ for (const char *p = value; *p; p++) {
507
+ switch (*p) {
508
+ case '&':
509
+ length += strlen("&amp;");
510
+ break;
511
+ case '"':
512
+ length += strlen("&quot;");
513
+ break;
514
+ case '<':
515
+ length += strlen("&lt;");
516
+ break;
517
+ case '>':
518
+ length += strlen("&gt;");
519
+ break;
520
+ default:
521
+ length++;
522
+ break;
523
+ }
524
+ }
525
+
526
+ char *escaped = malloc(length + 1);
527
+ if (!escaped) return NULL;
528
+
529
+ char *write = escaped;
530
+ for (const char *p = value; *p; p++) {
531
+ const char *replacement = NULL;
532
+ switch (*p) {
533
+ case '&':
534
+ replacement = "&amp;";
535
+ break;
536
+ case '"':
537
+ replacement = "&quot;";
538
+ break;
539
+ case '<':
540
+ replacement = "&lt;";
541
+ break;
542
+ case '>':
543
+ replacement = "&gt;";
544
+ break;
545
+ default:
546
+ *write++ = *p;
547
+ break;
548
+ }
549
+ if (replacement) {
550
+ size_t replacement_length = strlen(replacement);
551
+ memcpy(write, replacement, replacement_length);
552
+ write += replacement_length;
553
+ }
554
+ }
555
+ *write = '\0';
556
+ return escaped;
557
+ }
558
+
559
+ static apex_attributes *attributes_from_bear(
560
+ const apex_bear_image_attrs *bear) {
561
+ apex_attributes *attrs = create_attributes();
562
+ if (!attrs) return NULL;
563
+
564
+ for (size_t i = 0; i < bear->count; i++) {
565
+ /*
566
+ * Width/height classification is unchanged by escaping: all encoded
567
+ * characters are non-numeric in the decoded value as well.
568
+ */
569
+ char *escaped = escape_bear_attribute_value(bear->items[i].value);
570
+ if (escaped) {
571
+ add_attribute(attrs, bear->items[i].key, escaped);
572
+ free(escaped);
573
+ }
574
+ }
575
+ return attrs;
576
+ }
577
+
578
+ static apex_attributes *parse_adjacent_bear_attrs(
579
+ const char *after_construct,
580
+ const char *line_end,
581
+ const char **comment_start,
582
+ const char **comment_end) {
583
+ *comment_start = NULL;
584
+ *comment_end = NULL;
585
+
586
+ const char *candidate = after_construct;
587
+ while (candidate < line_end &&
588
+ (*candidate == ' ' || *candidate == '\t')) {
589
+ candidate++;
590
+ }
591
+
592
+ apex_bear_image_attrs bear = {0};
593
+ if (!apex_parse_bear_image_comment(
594
+ candidate, line_end, comment_end, &bear)) {
595
+ return NULL;
596
+ }
597
+
598
+ apex_attributes *attrs = attributes_from_bear(&bear);
599
+ apex_free_bear_image_attrs(&bear);
600
+ if (!attrs) {
601
+ *comment_end = NULL;
602
+ return NULL;
603
+ }
604
+
605
+ *comment_start = candidate;
606
+ return attrs;
607
+ }
608
+
480
609
  /**
481
610
  * Check if text ends with IAL pattern
482
611
  * Pattern: {: attributes} or {:.class} or {: ref-name} or {: ref-name .class #id}
@@ -1571,6 +1700,45 @@ static bool is_ial_line(const char *line, size_t len) {
1571
1700
  return (after >= end);
1572
1701
  }
1573
1702
 
1703
+ /** True if content at p looks like a list marker (- , * , + , or digit+. ) */
1704
+ static int ial_looks_like_list_marker(const char *p) {
1705
+ if (!*p) return 0;
1706
+ if (*p == '-' || *p == '*' || *p == '+')
1707
+ return (p[1] == ' ' || p[1] == '\t');
1708
+ if (isdigit((unsigned char)*p)) {
1709
+ while (isdigit((unsigned char)*p)) p++;
1710
+ return (*p == '.' && (p[1] == ' ' || p[1] == '\t'));
1711
+ }
1712
+ return 0;
1713
+ }
1714
+
1715
+ /** True if line is an indented code block (4+ spaces or tab at start). */
1716
+ static bool ial_line_is_indented_code_block(const char *line, size_t len) {
1717
+ if (len == 0) return false;
1718
+ if (line[0] == '\t')
1719
+ return !ial_looks_like_list_marker(line + 1);
1720
+ if (len < 4 || line[0] != ' ' || line[1] != ' ' || line[2] != ' ' || line[3] != ' ')
1721
+ return false;
1722
+ const char *content = line + 4;
1723
+ while (content < line + len && *content == ' ') content++;
1724
+ return content < line + len && !ial_looks_like_list_marker(content);
1725
+ }
1726
+
1727
+ /** True if line is a fenced code fence (``` or ~~~) with up to 3 leading spaces. */
1728
+ static bool ial_line_is_fence(const char *line, size_t len) {
1729
+ const char *p = line;
1730
+ const char *end = line + len;
1731
+ int spaces = 0;
1732
+ while (p < end && *p == ' ' && spaces < 3) {
1733
+ p++;
1734
+ spaces++;
1735
+ }
1736
+ if (p + 2 >= end) return false;
1737
+ if (p[0] == '`' && p[1] == '`' && p[2] == '`') return true;
1738
+ if (p[0] == '~' && p[1] == '~' && p[2] == '~') return true;
1739
+ return false;
1740
+ }
1741
+
1574
1742
  /**
1575
1743
  * Preprocess text to separate IAL markers from preceding content.
1576
1744
  * Kramdown allows IAL on the line immediately following content,
@@ -1590,6 +1758,8 @@ char *apex_preprocess_ial(const char *text) {
1590
1758
  const char *p = text;
1591
1759
  bool prev_line_was_content = false;
1592
1760
  bool prev_line_was_blank = true; /* Start as if there was a blank line before */
1761
+ bool in_fenced_code = false;
1762
+ bool in_indented_code = false;
1593
1763
 
1594
1764
  while (*p) {
1595
1765
  /* Find end of current line */
@@ -1613,6 +1783,18 @@ char *apex_preprocess_ial(const char *text) {
1613
1783
  /* Check if this line is an IAL */
1614
1784
  bool is_ial = is_ial_line(line_start, line_len);
1615
1785
 
1786
+ if (ial_line_is_fence(line_start, line_len)) {
1787
+ in_fenced_code = !in_fenced_code;
1788
+ }
1789
+ if (!in_fenced_code) {
1790
+ if (ial_line_is_indented_code_block(line_start, line_len)) {
1791
+ in_indented_code = true;
1792
+ } else if (!is_blank) {
1793
+ in_indented_code = false;
1794
+ }
1795
+ }
1796
+ bool in_code = in_fenced_code || in_indented_code;
1797
+
1616
1798
  /* Special case: Kramdown-style TOC marker "{:toc ...}".
1617
1799
  *
1618
1800
  * In Kramdown/Jekyll, a pure IAL paragraph containing only "{:toc}"
@@ -1622,7 +1804,7 @@ char *apex_preprocess_ial(const char *text) {
1622
1804
  * TOC extension.
1623
1805
  */
1624
1806
  bool handled_toc_marker = false;
1625
- if (is_ial) {
1807
+ if (is_ial && !in_code) {
1626
1808
  const char *q = line_start;
1627
1809
  const char *end = line_start + line_len;
1628
1810
 
@@ -2495,12 +2677,17 @@ static char *attributes_to_markdown(apex_attributes *attrs) {
2495
2677
  return attributes_to_inline_format(attrs);
2496
2678
  }
2497
2679
 
2680
+ static bool reference_id_matches(
2681
+ const char *ref_id, const char *text, size_t text_len);
2682
+
2498
2683
  /**
2499
2684
  * Find image attribute entry by reference name
2500
2685
  */
2501
2686
  static image_attr_entry *find_image_attr_by_ref(image_attr_entry *list, const char *ref_name) {
2502
2687
  for (image_attr_entry *entry = list; entry; entry = entry->next) {
2503
- if (entry->ref_name && ref_name && strcmp(entry->ref_name, ref_name) == 0) {
2688
+ if (entry->ref_name && ref_name &&
2689
+ reference_id_matches(
2690
+ entry->ref_name, ref_name, strlen(ref_name))) {
2504
2691
  return entry;
2505
2692
  }
2506
2693
  }
@@ -2607,17 +2794,15 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
2607
2794
  }
2608
2795
 
2609
2796
  /* Check if we should do URL encoding */
2610
- bool do_url_encoding = (mode == APEX_MODE_UNIFIED ||
2797
+ bool do_url_encoding = apex_mode_is_unified_family(mode) ||
2611
2798
  mode == APEX_MODE_MULTIMARKDOWN ||
2612
- mode == APEX_MODE_KRAMDOWN);
2799
+ mode == APEX_MODE_KRAMDOWN;
2613
2800
 
2614
- /* Check if we should process image attributes.
2615
- * Enabled for Unified, MultiMarkdown, and GFM modes so that width/height/style
2616
- * and @2x markers on images and reference definitions are honored consistently.
2617
- */
2618
- bool do_image_attrs = (mode == APEX_MODE_UNIFIED ||
2801
+ /* Check if we should process image attributes. */
2802
+ bool do_image_attrs = apex_mode_is_unified_family(mode) ||
2619
2803
  mode == APEX_MODE_MULTIMARKDOWN ||
2620
- mode == APEX_MODE_GFM);
2804
+ mode == APEX_MODE_GFM ||
2805
+ mode == APEX_MODE_COMMONMARK;
2621
2806
 
2622
2807
  if (!do_url_encoding && !do_image_attrs) {
2623
2808
  /* Nothing to do */
@@ -2862,6 +3047,43 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
2862
3047
  }
2863
3048
  }
2864
3049
 
3050
+ /*
3051
+ * Bear stores image metadata in an adjacent HTML
3052
+ * comment. Parse and merge it, but leave the source
3053
+ * bytes untouched so cmark preserves the comment.
3054
+ * Gated on do_image_attrs so modes that only enter
3055
+ * the preprocessor for URL encoding (Kramdown) do
3056
+ * not pick up Bear attributes.
3057
+ */
3058
+ if (do_image_attrs) {
3059
+ const char *after_attributes =
3060
+ ial_end_pos ? ial_end_pos + 1 : after_paren;
3061
+ const char *line_end = after_attributes;
3062
+ while (*line_end &&
3063
+ *line_end != '\n' && *line_end != '\r') {
3064
+ line_end++;
3065
+ }
3066
+ const char *bear_comment_start = NULL;
3067
+ const char *bear_comment_end = NULL;
3068
+ apex_attributes *bear_attrs =
3069
+ parse_adjacent_bear_attrs(
3070
+ after_attributes,
3071
+ line_end,
3072
+ &bear_comment_start,
3073
+ &bear_comment_end);
3074
+ if (bear_attrs) {
3075
+ if (attrs) {
3076
+ apex_attributes *merged =
3077
+ merge_attributes(attrs, bear_attrs);
3078
+ apex_free_attributes(attrs);
3079
+ apex_free_attributes(bear_attrs);
3080
+ attrs = merged;
3081
+ } else {
3082
+ attrs = bear_attrs;
3083
+ }
3084
+ }
3085
+ }
3086
+
2865
3087
  /* URL ending in .* means auto-discover formats (same as auto attribute) */
2866
3088
  bool url_is_wildcard = (url_len >= 2 && url[url_len - 2] == '.' && url[url_len - 1] == '*');
2867
3089
  if (url_is_wildcard && do_image_attrs) {
@@ -3038,6 +3260,38 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
3038
3260
  /* Find the end of the line first */
3039
3261
  const char *line_end = p;
3040
3262
  while (*line_end && *line_end != '\n' && *line_end != '\r') line_end++;
3263
+ const char *physical_line_end = line_end;
3264
+ const char *bear_comment_start = NULL;
3265
+ const char *bear_comment_end = NULL;
3266
+ const char *bear_output_start = NULL;
3267
+ apex_attributes *bear_attrs = NULL;
3268
+
3269
+ /* Pre-scan for a candidate Bear comment so title/attribute
3270
+ * parsing below does not consume it. Adjacency to the true
3271
+ * end of the definition is verified after that parsing.
3272
+ */
3273
+ if (do_image_attrs) {
3274
+ const char *bear_candidate = strstr(url_start, "<!--");
3275
+ while (bear_candidate &&
3276
+ bear_candidate < physical_line_end) {
3277
+ bear_attrs = parse_adjacent_bear_attrs(
3278
+ bear_candidate,
3279
+ physical_line_end,
3280
+ &bear_comment_start,
3281
+ &bear_comment_end);
3282
+ if (bear_attrs) break;
3283
+ bear_candidate = strstr(bear_candidate + 1, "<!--");
3284
+ }
3285
+ if (bear_attrs) {
3286
+ bear_output_start = bear_comment_start;
3287
+ while (bear_output_start > url_start &&
3288
+ (bear_output_start[-1] == ' ' ||
3289
+ bear_output_start[-1] == '\t')) {
3290
+ bear_output_start--;
3291
+ }
3292
+ line_end = bear_output_start;
3293
+ }
3294
+ }
3041
3295
 
3042
3296
  /* For reference definitions, we need to check for:
3043
3297
  * 1. Image attributes (if do_image_attrs): key=value pattern
@@ -3309,6 +3563,63 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
3309
3563
  }
3310
3564
  }
3311
3565
 
3566
+ if (bear_attrs) {
3567
+ /* Enforce adjacency: re-scan from the true end of
3568
+ * the URL, title, IAL, or MMD attributes so only
3569
+ * spaces or tabs may precede the Bear comment.
3570
+ * The URL itself ends at its first whitespace,
3571
+ * matching cmark link destinations.
3572
+ */
3573
+ const char *after_construct;
3574
+ if (title_end) {
3575
+ after_construct = title_end;
3576
+ } else {
3577
+ after_construct = url_start;
3578
+ while (after_construct < url_end &&
3579
+ *after_construct != ' ' &&
3580
+ *after_construct != '\t') {
3581
+ after_construct++;
3582
+ }
3583
+ }
3584
+ const char *adjacent_start = NULL;
3585
+ const char *adjacent_end = NULL;
3586
+ apex_attributes *adjacent_attrs =
3587
+ parse_adjacent_bear_attrs(
3588
+ after_construct,
3589
+ physical_line_end,
3590
+ &adjacent_start,
3591
+ &adjacent_end);
3592
+ apex_free_attributes(bear_attrs);
3593
+ bear_attrs = adjacent_attrs;
3594
+ if (bear_attrs) {
3595
+ bear_comment_start = adjacent_start;
3596
+ bear_comment_end = adjacent_end;
3597
+ bear_output_start = adjacent_start;
3598
+ while (bear_output_start > after_construct &&
3599
+ (bear_output_start[-1] == ' ' ||
3600
+ bear_output_start[-1] == '\t')) {
3601
+ bear_output_start--;
3602
+ }
3603
+ } else {
3604
+ bear_comment_start = NULL;
3605
+ bear_comment_end = NULL;
3606
+ bear_output_start = NULL;
3607
+ }
3608
+ }
3609
+
3610
+ if (bear_attrs) {
3611
+ if (attrs) {
3612
+ apex_attributes *merged =
3613
+ merge_attributes(attrs, bear_attrs);
3614
+ apex_free_attributes(attrs);
3615
+ apex_free_attributes(bear_attrs);
3616
+ attrs = merged;
3617
+ } else {
3618
+ attrs = bear_attrs;
3619
+ }
3620
+ bear_attrs = NULL;
3621
+ }
3622
+
3312
3623
  /* Extract reference name */
3313
3624
  size_t ref_name_len = ref_end - ref_start - 1; /* Exclude [ and ] */
3314
3625
  char *ref_name = malloc(ref_name_len + 1);
@@ -3343,8 +3654,8 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
3343
3654
  */
3344
3655
  if (is_footnote_ref) {
3345
3656
  /* Write the entire definition line unchanged */
3346
- size_t line_len = line_end - ref_start;
3347
- if (*line_end == '\n') {
3657
+ size_t line_len = physical_line_end - ref_start;
3658
+ if (*physical_line_end == '\n') {
3348
3659
  line_len++; /* Include newline */
3349
3660
  }
3350
3661
 
@@ -3370,7 +3681,7 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
3370
3681
  remaining -= line_len;
3371
3682
 
3372
3683
  /* Advance read past this line (including newline if present) */
3373
- const char *next = line_end;
3684
+ const char *next = physical_line_end;
3374
3685
  if (*next == '\n') {
3375
3686
  next++;
3376
3687
  } else if (*next == '\r') {
@@ -3393,11 +3704,13 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
3393
3704
  bool skip_encode_ref = has_protocol(url);
3394
3705
  char *encoded_url = (do_url_encoding && !skip_encode_ref) ? url_encode(url) : strdup(url);
3395
3706
  if (encoded_url) {
3707
+ bool has_bear_attrs = (bear_comment_start != NULL);
3396
3708
  bool has_image_attrs = (attrs != NULL && attrs_are_image_specific(attrs));
3397
3709
  /* If has image-specific attributes (width, height, etc.), store with reference name.
3398
3710
  * Don't create entry for link refs with only a title (e.g. [ref]: url "title"). */
3399
3711
  if (ref_name) {
3400
- if (has_image_attrs || (do_image_attrs && found_ial)) {
3712
+ if (has_bear_attrs || has_image_attrs ||
3713
+ (do_image_attrs && found_ial)) {
3401
3714
  image_attr_entry *entry = create_image_attr_entry_with_ref(&local_img_attrs, encoded_url, ref_name);
3402
3715
  if (entry) {
3403
3716
  if (attrs) {
@@ -3412,8 +3725,11 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
3412
3725
  add_class(entry->attrs, attrs->classes[i]);
3413
3726
  }
3414
3727
  }
3415
- /* Add title if present */
3416
- if (title_text) {
3728
+ /* Add the markdown title unless Bear
3729
+ * metadata already supplied one. */
3730
+ if (title_text &&
3731
+ find_attribute_index(
3732
+ entry->attrs, "title") < 0) {
3417
3733
  add_attribute(entry->attrs, "title", title_text);
3418
3734
  }
3419
3735
  /* Entry created - will be used for expansion */
@@ -3421,22 +3737,72 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
3421
3737
  }
3422
3738
  }
3423
3739
  /* If this reference definition has image-specific attributes, remove it so we expand and apply attrs */
3424
- bool created_entry = (ref_name && (has_image_attrs || (do_image_attrs && found_ial)));
3740
+ bool created_entry =
3741
+ (ref_name &&
3742
+ (has_bear_attrs || has_image_attrs ||
3743
+ (do_image_attrs && found_ial)));
3425
3744
  bool should_remove = created_entry;
3426
3745
 
3427
3746
  if (should_remove) {
3428
3747
  /* Reference definitions with attributes are removed from output (like ALDs) */
3429
- /* Skip the entire line - don't write anything back */
3748
+ /* Preserve Bear metadata as one standalone raw HTML comment. */
3749
+ bool wrote_bear_comment = false;
3750
+ if (bear_output_start && bear_comment_end) {
3751
+ size_t comment_len =
3752
+ (size_t)(bear_comment_end - bear_output_start);
3753
+ if (comment_len + 2 > remaining) {
3754
+ size_t written = write - output;
3755
+ size_t new_capacity =
3756
+ (written + comment_len + 3) * 2;
3757
+ char *new_output =
3758
+ realloc(output, new_capacity);
3759
+ if (!new_output) {
3760
+ free(output);
3761
+ free(url);
3762
+ free(encoded_url);
3763
+ free(ref_name);
3764
+ if (title_text) free(title_text);
3765
+ if (attrs) apex_free_attributes(attrs);
3766
+ apex_free_image_attributes(local_img_attrs);
3767
+ return NULL;
3768
+ }
3769
+ output = new_output;
3770
+ write = output + written;
3771
+ remaining = new_capacity - written;
3772
+ }
3773
+ memcpy(write, bear_output_start, comment_len);
3774
+ write += comment_len;
3775
+ remaining -= comment_len;
3776
+ wrote_bear_comment = true;
3777
+ }
3430
3778
  free(ref_name);
3431
3779
  if (title_text) free(title_text);
3432
- const char *p = line_end;
3433
- /* Skip the newline */
3780
+ /* A preserved comment keeps the original line
3781
+ * ending so the next block stays separate.
3782
+ * Otherwise the whole line is removed.
3783
+ */
3784
+ const char *p = physical_line_end;
3434
3785
  if (*p == '\n') {
3786
+ if (wrote_bear_comment && remaining > 0) {
3787
+ *write++ = '\n';
3788
+ remaining--;
3789
+ }
3435
3790
  p++;
3436
3791
  } else if (*p == '\r') {
3437
3792
  if (p[1] == '\n') {
3793
+ if (wrote_bear_comment &&
3794
+ remaining >= 2) {
3795
+ *write++ = '\r';
3796
+ *write++ = '\n';
3797
+ remaining -= 2;
3798
+ }
3438
3799
  p += 2;
3439
3800
  } else {
3801
+ if (wrote_bear_comment &&
3802
+ remaining > 0) {
3803
+ *write++ = '\r';
3804
+ remaining--;
3805
+ }
3440
3806
  p++;
3441
3807
  }
3442
3808
  }
@@ -3505,7 +3871,10 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
3505
3871
  }
3506
3872
  }
3507
3873
 
3508
- /* Advance read past the line (including IAL if it was processed) */
3874
+ /* Advance read past the processed portion so
3875
+ * any remaining line text (for example a
3876
+ * rejected comment) is copied by the main
3877
+ * loop. */
3509
3878
  const char *p = title_end ? title_end : line_end;
3510
3879
 
3511
3880
  /* Write newline */
@@ -3537,6 +3906,13 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
3537
3906
  continue;
3538
3907
  }
3539
3908
  }
3909
+
3910
+ /* URL extraction failed or was empty; release any pre-scanned
3911
+ * Bear attributes that were never merged. */
3912
+ if (bear_attrs) {
3913
+ apex_free_attributes(bear_attrs);
3914
+ bear_attrs = NULL;
3915
+ }
3540
3916
  }
3541
3917
  }
3542
3918
 
@@ -3735,27 +4111,40 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
3735
4111
  bool made_expansions = false;
3736
4112
 
3737
4113
  while (*read2) {
3738
- /* Look for reference-style images: ![alt][ref] */
4114
+ /* Look for full, collapsed, and shortcut reference images. */
3739
4115
  if (*read2 == '!' && read2[1] == '[') {
3740
4116
  const char *img_start = read2;
3741
4117
  read2 += 2; /* Skip ![ */
3742
4118
 
3743
4119
  /* Find closing ] for alt text */
3744
4120
  const char *alt_end = strchr(read2, ']');
3745
- if (alt_end && alt_end[1] == '[') {
3746
- /* Found ![alt][ */
3747
- const char *ref_start = alt_end + 2; /* After ][ */
3748
- const char *ref_end = strchr(ref_start, ']');
3749
- if (ref_end) {
3750
- /* Extract reference name (strip surrounding [ ]) */
3751
- const char *name_start = (*ref_start == '[') ? ref_start + 1 : ref_start;
3752
- if (name_start > ref_end) {
3753
- name_start = ref_start;
4121
+ if (alt_end && alt_end[1] != '(') {
4122
+ const char *ref_start = read2;
4123
+ const char *ref_end = alt_end;
4124
+ const char *image_end = alt_end;
4125
+ bool is_explicit_reference = false;
4126
+ if (alt_end[1] == '[') {
4127
+ const char *explicit_ref_start = alt_end + 2;
4128
+ const char *explicit_ref_end =
4129
+ strchr(explicit_ref_start, ']');
4130
+ if (!explicit_ref_end) {
4131
+ ref_end = NULL;
4132
+ } else if (explicit_ref_end > explicit_ref_start) {
4133
+ ref_start = explicit_ref_start;
4134
+ ref_end = explicit_ref_end;
4135
+ image_end = explicit_ref_end;
4136
+ is_explicit_reference = true;
4137
+ } else {
4138
+ image_end = explicit_ref_end;
3754
4139
  }
3755
- size_t ref_name_len = (size_t)(ref_end - name_start);
4140
+ }
4141
+
4142
+ if (ref_end) {
4143
+ size_t ref_name_len =
4144
+ (size_t)(ref_end - ref_start);
3756
4145
  char *ref_name = malloc(ref_name_len + 1);
3757
4146
  if (ref_name) {
3758
- memcpy(ref_name, name_start, ref_name_len);
4147
+ memcpy(ref_name, ref_start, ref_name_len);
3759
4148
  ref_name[ref_name_len] = '\0';
3760
4149
  /* Trim whitespace from reference name */
3761
4150
  char *p = ref_name;
@@ -3769,9 +4158,53 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
3769
4158
  p--;
3770
4159
  }
3771
4160
 
4161
+ apex_attributes *local_attrs = NULL;
4162
+ if (is_explicit_reference) {
4163
+ const char *after_use = image_end + 1;
4164
+ const char *line_end = after_use;
4165
+ while (*line_end && *line_end != '\n' &&
4166
+ *line_end != '\r') {
4167
+ line_end++;
4168
+ }
4169
+ const char *comment_start = NULL;
4170
+ const char *comment_end = NULL;
4171
+ local_attrs = parse_adjacent_bear_attrs(
4172
+ after_use,
4173
+ line_end,
4174
+ &comment_start,
4175
+ &comment_end);
4176
+ }
4177
+
3772
4178
  /* Look up if this reference has attributes */
3773
- image_attr_entry *def_entry = find_image_attr_by_ref(local_img_attrs, ref_name);
4179
+ image_attr_entry *def_entry =
4180
+ find_image_attr_by_ref(
4181
+ local_img_attrs, ref_name);
3774
4182
  if (def_entry && def_entry->url) {
4183
+ apex_attributes *effective_attrs =
4184
+ copy_attributes(def_entry->attrs);
4185
+ if (effective_attrs && local_attrs) {
4186
+ for (int i = 0;
4187
+ i < local_attrs->attr_count;
4188
+ i++) {
4189
+ int existing_idx =
4190
+ find_attribute_index(
4191
+ effective_attrs,
4192
+ local_attrs->keys[i]);
4193
+ if (existing_idx >= 0) {
4194
+ free(effective_attrs
4195
+ ->values[existing_idx]);
4196
+ effective_attrs
4197
+ ->values[existing_idx] =
4198
+ strdup(
4199
+ local_attrs->values[i]);
4200
+ } else {
4201
+ add_attribute(
4202
+ effective_attrs,
4203
+ local_attrs->keys[i],
4204
+ local_attrs->values[i]);
4205
+ }
4206
+ }
4207
+ }
3775
4208
  if (getenv("APEX_DEBUG_PIPELINE")) {
3776
4209
  fprintf(stderr, "[APEX_DEBUG] expand ref [%s] -> inline image\n", ref_name);
3777
4210
  }
@@ -3780,7 +4213,11 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
3780
4213
  size_t alt_len = alt_end - read2;
3781
4214
 
3782
4215
  /* Convert attributes to markdown format */
3783
- char *attr_str = attributes_to_markdown(def_entry->attrs);
4216
+ char *attr_str =
4217
+ effective_attrs
4218
+ ? attributes_to_markdown(
4219
+ effective_attrs)
4220
+ : NULL;
3784
4221
 
3785
4222
  /* Build expanded image: ![alt](url attributes) */
3786
4223
  /* Need space for: ![ + alt + ]( + url + space + attr_str + ) */
@@ -3831,13 +4268,17 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
3831
4268
  *write2++ = ')';
3832
4269
  remaining2--;
3833
4270
 
3834
- read2 = ref_end + 1;
4271
+ read2 = image_end + 1;
3835
4272
  free(ref_name);
3836
4273
  free(attr_str);
4274
+ apex_free_attributes(local_attrs);
4275
+ apex_free_attributes(effective_attrs);
3837
4276
  continue;
3838
4277
  }
3839
4278
  free(attr_str);
4279
+ apex_free_attributes(effective_attrs);
3840
4280
  }
4281
+ apex_free_attributes(local_attrs);
3841
4282
  free(ref_name);
3842
4283
  }
3843
4284
  }
@@ -4185,19 +4626,20 @@ static bool reference_id_matches(const char *ref_id, const char *text, size_t te
4185
4626
  remaining--;
4186
4627
  }
4187
4628
 
4188
- /* Compare character by character (case-insensitive) */
4629
+ /* Compare case-insensitively, collapsing each whitespace run. */
4189
4630
  while (*p && remaining > 0) {
4190
- if (tolower((unsigned char)*p) != tolower((unsigned char)*t)) {
4191
- /* Check if difference is just whitespace */
4192
- if (isspace((unsigned char)*p) && isspace((unsigned char)*t)) {
4193
- /* Both are whitespace - skip and continue */
4194
- while (*p && isspace((unsigned char)*p)) p++;
4195
- while (remaining > 0 && isspace((unsigned char)*t)) {
4196
- t++;
4197
- remaining--;
4198
- }
4199
- continue;
4631
+ bool p_space = isspace((unsigned char)*p);
4632
+ bool t_space = isspace((unsigned char)*t);
4633
+ if (p_space || t_space) {
4634
+ if (!p_space || !t_space) return false;
4635
+ while (*p && isspace((unsigned char)*p)) p++;
4636
+ while (remaining > 0 && isspace((unsigned char)*t)) {
4637
+ t++;
4638
+ remaining--;
4200
4639
  }
4640
+ continue;
4641
+ }
4642
+ if (tolower((unsigned char)*p) != tolower((unsigned char)*t)) {
4201
4643
  return false;
4202
4644
  }
4203
4645
  p++;
@@ -4378,12 +4820,20 @@ char *apex_preprocess_bracketed_spans(const char *text) {
4378
4820
  if (needs_markdown_span) {
4379
4821
  /* Write <span markdown="span" ...> for spans that
4380
4822
  * genuinely need inline markdown processing. */
4381
- written = snprintf(write, remaining, "<span markdown=\"span\"%s>", attr_str);
4823
+ if (attr_str && attr_str[0]) {
4824
+ written = snprintf(write, remaining, "<span markdown=\"span\" %s>", attr_str);
4825
+ } else {
4826
+ written = snprintf(write, remaining, "<span markdown=\"span\">");
4827
+ }
4382
4828
  } else {
4383
4829
  /* For simple text-only spans, omit markdown=\"span\"
4384
4830
  * so that content like a lone '-' is not reparsed
4385
4831
  * as a list item by the markdown-in-HTML pipeline. */
4386
- written = snprintf(write, remaining, "<span%s>", attr_str);
4832
+ if (attr_str && attr_str[0]) {
4833
+ written = snprintf(write, remaining, "<span %s>", attr_str);
4834
+ } else {
4835
+ written = snprintf(write, remaining, "<span>");
4836
+ }
4387
4837
  }
4388
4838
 
4389
4839
  if (written > 0 && (size_t)written < remaining) {