apex-ruby 1.0.8 → 1.0.9

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/ext/apex_ext/apex_src/CHANGELOG.md +69 -0
  3. data/ext/apex_ext/apex_src/CMakeLists.txt +2 -1
  4. data/ext/apex_ext/apex_src/Formula/apex.rb +2 -2
  5. data/ext/apex_ext/apex_src/Package.swift +14 -2
  6. data/ext/apex_ext/apex_src/README.md +12 -9
  7. data/ext/apex_ext/apex_src/VERSION +1 -1
  8. data/ext/apex_ext/apex_src/cli/main.c +625 -98
  9. data/ext/apex_ext/apex_src/ial.html +24 -0
  10. data/ext/apex_ext/apex_src/include/apex/apex.h +57 -7
  11. data/ext/apex_ext/apex_src/include/apex/ast_markdown.h +3 -0
  12. data/ext/apex_ext/apex_src/include/apex/module.modulemap +8 -0
  13. data/ext/apex_ext/apex_src/include/apexc.h +6 -0
  14. data/ext/apex_ext/apex_src/include/module.modulemap +4 -0
  15. data/ext/apex_ext/apex_src/man/apex-config.5 +8 -2
  16. data/ext/apex_ext/apex_src/man/apex-plugins.7 +13 -13
  17. data/ext/apex_ext/apex_src/man/apex.1 +150 -442
  18. data/ext/apex_ext/apex_src/man/apex.1.md +13 -0
  19. data/ext/apex_ext/apex_src/src/_README.md +3 -1
  20. data/ext/apex_ext/apex_src/src/apex.c +151 -6
  21. data/ext/apex_ext/apex_src/src/ast_terminal.c +459 -8
  22. data/ext/apex_ext/apex_src/src/extensions/advanced_tables.c +6 -6
  23. data/ext/apex_ext/apex_src/src/extensions/callouts.c +1 -1
  24. data/ext/apex_ext/apex_src/src/extensions/citations.c +24 -12
  25. data/ext/apex_ext/apex_src/src/extensions/critic.c +14 -6
  26. data/ext/apex_ext/apex_src/src/extensions/emoji.c +2 -2
  27. data/ext/apex_ext/apex_src/src/extensions/grid_tables.c +1 -1
  28. data/ext/apex_ext/apex_src/src/extensions/header_ids.c +19 -6
  29. data/ext/apex_ext/apex_src/src/extensions/ial.c +25 -13
  30. data/ext/apex_ext/apex_src/src/extensions/includes.c +7 -7
  31. data/ext/apex_ext/apex_src/src/extensions/index.c +19 -7
  32. data/ext/apex_ext/apex_src/src/extensions/inline_footnotes.c +2 -2
  33. data/ext/apex_ext/apex_src/src/extensions/insert.c +1 -1
  34. data/ext/apex_ext/apex_src/src/extensions/math.c +11 -2
  35. data/ext/apex_ext/apex_src/src/extensions/metadata.c +46 -0
  36. data/ext/apex_ext/apex_src/src/extensions/metadata.h +12 -0
  37. data/ext/apex_ext/apex_src/src/html_renderer.c +2 -2
  38. data/ext/apex_ext/apex_src/src/plugins.c +97 -55
  39. data/ext/apex_ext/apex_src/src/plugins.h +0 -10
  40. data/ext/apex_ext/apex_src/src/pretty_html.c +1 -1
  41. data/ext/apex_ext/apex_src/tests/fixtures/metadata/mmd-metadata.md +5 -0
  42. data/ext/apex_ext/apex_src/tests/fixtures/metadata/pandoc-meta.md +4 -0
  43. data/ext/apex_ext/apex_src/tests/fixtures/metadata/yaml-frontmatter.md +6 -0
  44. data/ext/apex_ext/apex_src/tests/metadata_cli_test.sh +119 -0
  45. data/ext/apex_ext/apex_src/tests/test_custom_plugins.c +78 -0
  46. data/ext/apex_ext/apex_src/tests/test_extensions.c +27 -0
  47. data/ext/apex_ext/apex_src/tests/test_metadata.c +42 -0
  48. data/ext/apex_ext/apex_src/tests/test_output.c +83 -0
  49. data/ext/apex_ext/apex_src/tests/test_runner.c +4 -1
  50. data/lib/apex/version.rb +1 -1
  51. metadata +10 -2
@@ -27,6 +27,8 @@
27
27
  #include <sys/types.h>
28
28
  #include <sys/wait.h>
29
29
  #include <errno.h>
30
+ #include <limits.h>
31
+ #include <sys/stat.h>
30
32
 
31
33
  #ifdef APEX_HAVE_LIBYAML
32
34
  #include <yaml.h>
@@ -1073,6 +1075,13 @@ static void serialize_inline(terminal_buffer *buf,
1073
1075
  const terminal_theme *theme,
1074
1076
  bool use_256_color);
1075
1077
 
1078
+ static void serialize_terminal_image_as_link(terminal_buffer *buf,
1079
+ cmark_node *image,
1080
+ const char *url,
1081
+ const apex_options *options,
1082
+ const terminal_theme *theme,
1083
+ bool use_256_color);
1084
+
1076
1085
  static void serialize_block(terminal_buffer *buf,
1077
1086
  cmark_node *node,
1078
1087
  const apex_options *options,
@@ -1197,6 +1206,372 @@ static const char *theme_style_for_node(const terminal_theme *theme,
1197
1206
  static const char *html_span_style_stack[16];
1198
1207
  static int html_span_style_depth = 0;
1199
1208
 
1209
+ /* ------------------------------------------------------------------------- */
1210
+ /* Terminal inline images (imgcat, chafa, viu, catimg) */
1211
+ /* ------------------------------------------------------------------------- */
1212
+
1213
+ typedef enum {
1214
+ TERM_IMG_NONE = 0,
1215
+ TERM_IMG_IMGCAT,
1216
+ TERM_IMG_CHAFA,
1217
+ TERM_IMG_VIU,
1218
+ TERM_IMG_CATIMG
1219
+ } term_img_tool_t;
1220
+
1221
+ static term_img_tool_t g_term_img_tool = TERM_IMG_NONE;
1222
+ static int g_term_img_width = 50;
1223
+ static bool g_term_has_curl = false;
1224
+
1225
+ static bool terminal_url_is_http(const char *url) {
1226
+ if (!url || !*url) {
1227
+ return false;
1228
+ }
1229
+ return (strncmp(url, "http://", 7) == 0 || strncmp(url, "https://", 8) == 0);
1230
+ }
1231
+
1232
+ static char *terminal_mkstemp_download_path(void) {
1233
+ const char *td = getenv("TMPDIR");
1234
+ if (!td || !*td) {
1235
+ td = "/tmp";
1236
+ }
1237
+ char *tmpl = (char *)malloc(PATH_MAX);
1238
+ if (!tmpl) {
1239
+ return NULL;
1240
+ }
1241
+ if ((size_t)snprintf(tmpl, PATH_MAX, "%s/apex-timg-XXXXXX", td) >= PATH_MAX) {
1242
+ free(tmpl);
1243
+ return NULL;
1244
+ }
1245
+ int fd = mkstemp(tmpl);
1246
+ if (fd < 0) {
1247
+ free(tmpl);
1248
+ return NULL;
1249
+ }
1250
+ close(fd);
1251
+ return tmpl;
1252
+ }
1253
+
1254
+ static bool terminal_curl_download(const char *url, const char *dest_path) {
1255
+ pid_t pid = fork();
1256
+ if (pid == 0) {
1257
+ int dnw = open("/dev/null", O_WRONLY);
1258
+ if (dnw != -1) {
1259
+ dup2(dnw, STDOUT_FILENO);
1260
+ dup2(dnw, STDERR_FILENO);
1261
+ close(dnw);
1262
+ }
1263
+ int dnr = open("/dev/null", O_RDONLY);
1264
+ if (dnr != -1) {
1265
+ dup2(dnr, STDIN_FILENO);
1266
+ close(dnr);
1267
+ }
1268
+ execlp("curl", "curl", "-fsSL", "-m", "60", "-o", dest_path, url, (char *)NULL);
1269
+ _exit(127);
1270
+ }
1271
+ if (pid < 0) {
1272
+ return false;
1273
+ }
1274
+ int status;
1275
+ waitpid(pid, &status, 0);
1276
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
1277
+ unlink(dest_path);
1278
+ return false;
1279
+ }
1280
+ struct stat st;
1281
+ if (stat(dest_path, &st) != 0 || !S_ISREG(st.st_mode) || st.st_size == 0) {
1282
+ unlink(dest_path);
1283
+ return false;
1284
+ }
1285
+ if (st.st_size > 10 * 1024 * 1024) {
1286
+ unlink(dest_path);
1287
+ return false;
1288
+ }
1289
+ return true;
1290
+ }
1291
+
1292
+ /**
1293
+ * Download http(s) URL to a fresh temp file (caller must unlink + free).
1294
+ * Returns NULL on failure; curl errors are suppressed (stderr to /dev/null).
1295
+ */
1296
+ static char *terminal_fetch_http_image_temp(const char *url) {
1297
+ if (!url || !g_term_has_curl || !terminal_url_is_http(url)) {
1298
+ return NULL;
1299
+ }
1300
+ char *path = terminal_mkstemp_download_path();
1301
+ if (!path) {
1302
+ return NULL;
1303
+ }
1304
+ if (!terminal_curl_download(url, path)) {
1305
+ free(path);
1306
+ return NULL;
1307
+ }
1308
+ return path;
1309
+ }
1310
+
1311
+ static bool executable_on_path(const char *name) {
1312
+ const char *pathenv = getenv("PATH");
1313
+ if (!pathenv || !*pathenv || !name || !*name) {
1314
+ return false;
1315
+ }
1316
+ char buf[PATH_MAX];
1317
+ const char *p = pathenv;
1318
+ while (*p) {
1319
+ const char *colon = strchr(p, ':');
1320
+ size_t dirlen = colon ? (size_t)(colon - p) : strlen(p);
1321
+ if (dirlen == 0) {
1322
+ if (sizeof(buf) > strlen(name) + 3) {
1323
+ snprintf(buf, sizeof(buf), "./%s", name);
1324
+ if (access(buf, X_OK) == 0) {
1325
+ return true;
1326
+ }
1327
+ }
1328
+ } else if (dirlen + strlen(name) + 2 < sizeof(buf)) {
1329
+ memcpy(buf, p, dirlen);
1330
+ buf[dirlen] = '\0';
1331
+ if (buf[dirlen - 1] != '/') {
1332
+ snprintf(buf + dirlen, sizeof(buf) - dirlen, "/%s", name);
1333
+ } else {
1334
+ snprintf(buf + dirlen, sizeof(buf) - dirlen, "%s", name);
1335
+ }
1336
+ if (access(buf, X_OK) == 0) {
1337
+ return true;
1338
+ }
1339
+ }
1340
+ if (!colon) {
1341
+ break;
1342
+ }
1343
+ p = colon + 1;
1344
+ }
1345
+ return false;
1346
+ }
1347
+
1348
+ static term_img_tool_t probe_terminal_image_tool(void) {
1349
+ if (executable_on_path("imgcat")) {
1350
+ return TERM_IMG_IMGCAT;
1351
+ }
1352
+ if (executable_on_path("chafa")) {
1353
+ return TERM_IMG_CHAFA;
1354
+ }
1355
+ if (executable_on_path("viu")) {
1356
+ return TERM_IMG_VIU;
1357
+ }
1358
+ if (executable_on_path("catimg")) {
1359
+ return TERM_IMG_CATIMG;
1360
+ }
1361
+ return TERM_IMG_NONE;
1362
+ }
1363
+
1364
+ static char *terminal_resolve_image_path(const char *url, const char *base_dir) {
1365
+ if (!url || !*url) {
1366
+ return NULL;
1367
+ }
1368
+ if (strncmp(url, "http://", 7) == 0 || strncmp(url, "https://", 8) == 0) {
1369
+ return NULL;
1370
+ }
1371
+ if (strncmp(url, "file://", 7) == 0) {
1372
+ const char *p = url + 7;
1373
+ if (strncmp(p, "localhost", 9) == 0) {
1374
+ p += 9;
1375
+ }
1376
+ while (*p && *p != '/') {
1377
+ p++;
1378
+ }
1379
+ if (*p == '/') {
1380
+ return strdup(p);
1381
+ }
1382
+ return NULL;
1383
+ }
1384
+ if (strstr(url, "://")) {
1385
+ return NULL;
1386
+ }
1387
+ return apex_resolve_local_image_path(url, base_dir);
1388
+ }
1389
+
1390
+ static void collect_image_alt_text(cmark_node *image, char *out, size_t cap) {
1391
+ out[0] = '\0';
1392
+ if (!image || cap == 0) {
1393
+ return;
1394
+ }
1395
+ const char *lit = cmark_node_get_literal(image);
1396
+ if (lit && *lit) {
1397
+ snprintf(out, cap, "%s", lit);
1398
+ return;
1399
+ }
1400
+ size_t n = 0;
1401
+ for (cmark_node *ch = cmark_node_first_child(image); ch && n + 1 < cap; ch = cmark_node_next(ch)) {
1402
+ if (cmark_node_get_type(ch) == CMARK_NODE_TEXT) {
1403
+ const char *t = cmark_node_get_literal(ch);
1404
+ if (t) {
1405
+ size_t tl = strlen(t);
1406
+ size_t room = cap - 1 - n;
1407
+ if (tl > room) {
1408
+ tl = room;
1409
+ }
1410
+ memcpy(out + n, t, tl);
1411
+ n += tl;
1412
+ out[n] = '\0';
1413
+ }
1414
+ }
1415
+ }
1416
+ }
1417
+
1418
+ static void append_terminal_image_caption(terminal_buffer *buf,
1419
+ const char *resolved_path,
1420
+ const char *title,
1421
+ const char *alt,
1422
+ const apex_options *options,
1423
+ const terminal_theme *theme,
1424
+ bool use_256_color) {
1425
+ const char *base = resolved_path;
1426
+ const char *slash = strrchr(resolved_path, '/');
1427
+ if (slash) {
1428
+ base = slash + 1;
1429
+ }
1430
+
1431
+ char line[512];
1432
+ snprintf(line, sizeof(line), "%s", base ? base : resolved_path);
1433
+ size_t pos = strlen(line);
1434
+
1435
+ if (title && *title && pos + 4 < sizeof(line)) {
1436
+ snprintf(line + pos, sizeof(line) - pos, " — %s", title);
1437
+ pos = strlen(line);
1438
+ }
1439
+ if (options && !options->title_captions_only && alt && *alt) {
1440
+ if (!title || strcmp(alt, title) != 0) {
1441
+ if (pos + 4 < sizeof(line)) {
1442
+ snprintf(line + pos, sizeof(line) - pos, " — %s", alt);
1443
+ }
1444
+ }
1445
+ }
1446
+
1447
+ if (theme && theme->link_url) {
1448
+ apply_style_string(buf, theme->link_url, use_256_color);
1449
+ } else {
1450
+ apply_style_string(buf, "bright_black", use_256_color);
1451
+ }
1452
+ buffer_append_str(buf, line);
1453
+ append_ansi_reset(buf);
1454
+ buffer_append_str(buf, "\n");
1455
+ }
1456
+
1457
+ static bool run_terminal_image_tool(terminal_buffer *buf, term_img_tool_t tool, int width, const char *abspath) {
1458
+ char wstr[32];
1459
+ snprintf(wstr, sizeof(wstr), "%d", width);
1460
+
1461
+ const char *argv[8];
1462
+ int argc = 0;
1463
+ switch (tool) {
1464
+ case TERM_IMG_IMGCAT:
1465
+ argv[argc++] = "imgcat";
1466
+ argv[argc++] = "-W";
1467
+ argv[argc++] = wstr;
1468
+ argv[argc++] = (char *)abspath;
1469
+ break;
1470
+ case TERM_IMG_CHAFA:
1471
+ argv[argc++] = "chafa";
1472
+ argv[argc++] = "-s";
1473
+ argv[argc++] = wstr;
1474
+ argv[argc++] = (char *)abspath;
1475
+ break;
1476
+ case TERM_IMG_VIU:
1477
+ argv[argc++] = "viu";
1478
+ argv[argc++] = "-w";
1479
+ argv[argc++] = wstr;
1480
+ argv[argc++] = (char *)abspath;
1481
+ break;
1482
+ case TERM_IMG_CATIMG:
1483
+ argv[argc++] = "catimg";
1484
+ argv[argc++] = "-w";
1485
+ argv[argc++] = wstr;
1486
+ argv[argc++] = (char *)abspath;
1487
+ break;
1488
+ default:
1489
+ return false;
1490
+ }
1491
+ argv[argc] = NULL;
1492
+
1493
+ int out_pipe[2];
1494
+ if (pipe(out_pipe) != 0) {
1495
+ return false;
1496
+ }
1497
+
1498
+ pid_t pid = fork();
1499
+ if (pid == 0) {
1500
+ dup2(out_pipe[1], STDOUT_FILENO);
1501
+ int devnull = open("/dev/null", O_WRONLY);
1502
+ if (devnull != -1) {
1503
+ dup2(devnull, STDERR_FILENO);
1504
+ close(devnull);
1505
+ }
1506
+ close(out_pipe[0]);
1507
+ close(out_pipe[1]);
1508
+ execvp(argv[0], (char *const *)argv);
1509
+ _exit(127);
1510
+ }
1511
+
1512
+ if (pid < 0) {
1513
+ close(out_pipe[0]);
1514
+ close(out_pipe[1]);
1515
+ return false;
1516
+ }
1517
+
1518
+ close(out_pipe[1]);
1519
+
1520
+ size_t cap = 8192;
1521
+ size_t size = 0;
1522
+ char *out = malloc(cap);
1523
+ if (!out) {
1524
+ close(out_pipe[0]);
1525
+ waitpid(pid, NULL, 0);
1526
+ return false;
1527
+ }
1528
+
1529
+ for (;;) {
1530
+ if (size + 4096 > cap) {
1531
+ cap *= 2;
1532
+ char *nb = realloc(out, cap);
1533
+ if (!nb) {
1534
+ free(out);
1535
+ close(out_pipe[0]);
1536
+ waitpid(pid, NULL, 0);
1537
+ return false;
1538
+ }
1539
+ out = nb;
1540
+ }
1541
+ ssize_t n = read(out_pipe[0], out + size, 4096);
1542
+ if (n < 0) {
1543
+ if (errno == EINTR) {
1544
+ continue;
1545
+ }
1546
+ free(out);
1547
+ close(out_pipe[0]);
1548
+ waitpid(pid, NULL, 0);
1549
+ return false;
1550
+ }
1551
+ if (n == 0) {
1552
+ break;
1553
+ }
1554
+ size += (size_t)n;
1555
+ }
1556
+ close(out_pipe[0]);
1557
+
1558
+ int status;
1559
+ waitpid(pid, &status, 0);
1560
+
1561
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
1562
+ free(out);
1563
+ return false;
1564
+ }
1565
+
1566
+ bool need_nl = (size == 0 || out[size - 1] != '\n');
1567
+ buffer_append(buf, out, size);
1568
+ free(out);
1569
+ if (need_nl) {
1570
+ buffer_append_str(buf, "\n");
1571
+ }
1572
+ return true;
1573
+ }
1574
+
1200
1575
  static void serialize_inline(terminal_buffer *buf,
1201
1576
  cmark_node *node,
1202
1577
  const apex_options *options,
@@ -1343,15 +1718,47 @@ static void serialize_inline(terminal_buffer *buf,
1343
1718
  }
1344
1719
  case CMARK_NODE_IMAGE: {
1345
1720
  const char *url = cmark_node_get_url(node);
1346
- buffer_append_str(buf, "![");
1347
- for (cmark_node *child = cmark_node_first_child(node); child; child = cmark_node_next(child)) {
1348
- serialize_inline(buf, child, options, theme, use_256_color);
1721
+ bool did_inline = false;
1722
+ if (g_term_img_tool != TERM_IMG_NONE && url && options && options->terminal_inline_images &&
1723
+ isatty(STDOUT_FILENO)) {
1724
+ char *path_to_show = NULL;
1725
+ bool is_temp_http = false;
1726
+
1727
+ if (terminal_url_is_http(url)) {
1728
+ if (g_term_has_curl) {
1729
+ path_to_show = terminal_fetch_http_image_temp(url);
1730
+ is_temp_http = (path_to_show != NULL);
1731
+ }
1732
+ } else {
1733
+ path_to_show = terminal_resolve_image_path(url, options->base_directory);
1734
+ if (path_to_show) {
1735
+ struct stat st;
1736
+ if (stat(path_to_show, &st) != 0 || !S_ISREG(st.st_mode) ||
1737
+ access(path_to_show, R_OK) != 0) {
1738
+ free(path_to_show);
1739
+ path_to_show = NULL;
1740
+ }
1741
+ }
1742
+ }
1743
+
1744
+ if (path_to_show) {
1745
+ char altbuf[512];
1746
+ collect_image_alt_text(node, altbuf, sizeof(altbuf));
1747
+ const char *title = cmark_node_get_title(node);
1748
+ if (run_terminal_image_tool(buf, g_term_img_tool, g_term_img_width, path_to_show)) {
1749
+ append_terminal_image_caption(buf, path_to_show, title, altbuf, options, theme,
1750
+ use_256_color);
1751
+ buffer_append_str(buf, "\n");
1752
+ did_inline = true;
1753
+ }
1754
+ if (is_temp_http) {
1755
+ unlink(path_to_show);
1756
+ }
1757
+ free(path_to_show);
1758
+ }
1349
1759
  }
1350
- buffer_append_str(buf, "]");
1351
- if (url && *url) {
1352
- buffer_append_str(buf, "(");
1353
- buffer_append_str(buf, url);
1354
- buffer_append_str(buf, ")");
1760
+ if (!did_inline) {
1761
+ serialize_terminal_image_as_link(buf, node, url, options, theme, use_256_color);
1355
1762
  }
1356
1763
  break;
1357
1764
  }
@@ -1421,6 +1828,39 @@ static void serialize_inline(terminal_buffer *buf,
1421
1828
  }
1422
1829
  }
1423
1830
 
1831
+ /* Same visual pattern as CMARK_NODE_LINK: styled text then (url). */
1832
+ static void serialize_terminal_image_as_link(terminal_buffer *buf,
1833
+ cmark_node *image,
1834
+ const char *url,
1835
+ const apex_options *options,
1836
+ const terminal_theme *theme,
1837
+ bool use_256_color) {
1838
+ const char *class_style = theme_style_for_node(theme, image);
1839
+ if (class_style) {
1840
+ apply_style_string(buf, class_style, use_256_color);
1841
+ } else if (theme && theme->link_text) {
1842
+ apply_style_string(buf, theme->link_text, use_256_color);
1843
+ } else {
1844
+ apply_style_string(buf, "u b blue", use_256_color);
1845
+ }
1846
+ for (cmark_node *child = cmark_node_first_child(image); child; child = cmark_node_next(child)) {
1847
+ serialize_inline(buf, child, options, theme, use_256_color);
1848
+ }
1849
+ append_ansi_reset(buf);
1850
+ if (url && *url) {
1851
+ buffer_append_str(buf, " ");
1852
+ if (theme && theme->link_url) {
1853
+ apply_style_string(buf, theme->link_url, use_256_color);
1854
+ } else {
1855
+ apply_style_string(buf, "cyan", use_256_color);
1856
+ }
1857
+ buffer_append_str(buf, "(");
1858
+ buffer_append_str(buf, url);
1859
+ buffer_append_str(buf, ")");
1860
+ append_ansi_reset(buf);
1861
+ }
1862
+ }
1863
+
1424
1864
  static void serialize_block(terminal_buffer *buf,
1425
1865
  cmark_node *node,
1426
1866
  const apex_options *options,
@@ -2490,6 +2930,15 @@ char *apex_cmark_to_terminal(cmark_node *document,
2490
2930
  /* Reset HTML span style stack at the start of each render. */
2491
2931
  html_span_style_depth = 0;
2492
2932
 
2933
+ g_term_img_tool = TERM_IMG_NONE;
2934
+ g_term_img_width = 50;
2935
+ g_term_has_curl = executable_on_path("curl");
2936
+ if (options && options->terminal_inline_images && isatty(STDOUT_FILENO)) {
2937
+ int w = options->terminal_image_width > 0 ? options->terminal_image_width : 50;
2938
+ g_term_img_width = w;
2939
+ g_term_img_tool = probe_terminal_image_tool();
2940
+ }
2941
+
2493
2942
  terminal_theme *theme = load_theme(options);
2494
2943
  if (getenv("APEX_DEBUG_THEME")) {
2495
2944
  const char *tname = (options && options->theme_name) ? options->theme_name : "(null)";
@@ -2504,6 +2953,8 @@ char *apex_cmark_to_terminal(cmark_node *document,
2504
2953
 
2505
2954
  free_theme(theme);
2506
2955
 
2956
+ g_term_img_tool = TERM_IMG_NONE;
2957
+
2507
2958
  if (!buf.buf) {
2508
2959
  /* Fallback: empty string */
2509
2960
  char *empty = (char *)malloc(1);
@@ -209,7 +209,7 @@ static bool is_rowspan_cell(cmark_node *cell) {
209
209
  * Returns alignment type: "left", "right", "center", or NULL for default.
210
210
  * Modifies the cell's text content by removing the colons.
211
211
  */
212
- static char *process_cell_alignment(cmark_node *cell) {
212
+ static const char *process_cell_alignment(cmark_node *cell) {
213
213
  if (!cell) return NULL;
214
214
 
215
215
  /* Recursively find all text nodes in the cell */
@@ -259,7 +259,7 @@ static char *process_cell_alignment(cmark_node *cell) {
259
259
  }
260
260
 
261
261
  /* Determine alignment */
262
- char *align = NULL;
262
+ const char *align = NULL;
263
263
  if (has_leading_colon && has_trailing_colon) {
264
264
  align = "center";
265
265
  } else if (has_leading_colon) {
@@ -548,7 +548,7 @@ static void process_table_spans(cmark_node *table) {
548
548
  if (g_per_cell_alignment) {
549
549
  char *cell_attrs_check = (char *)cmark_node_get_user_data(cell);
550
550
  if (!cell_attrs_check || !strstr(cell_attrs_check, "data-remove")) {
551
- char *align = process_cell_alignment(cell);
551
+ const char *align = process_cell_alignment(cell);
552
552
  if (align) {
553
553
  /* Add style attribute for alignment */
554
554
  char *existing_attrs = (char *)cmark_node_get_user_data(cell);
@@ -931,8 +931,8 @@ static apex_attributes *parse_ial_from_text(const char *text) {
931
931
  if (!ial_end) return NULL;
932
932
 
933
933
  /* Parse IAL content */
934
- int content_len = ial_end - ial_start;
935
- if (content_len <= 0) return NULL;
934
+ size_t content_len = (size_t)(ial_end - ial_start);
935
+ if (content_len == 0) return NULL;
936
936
 
937
937
  /* Use parse_ial_content from ial.c - it already handles the content format */
938
938
  /* We need to access the static function, so we'll duplicate the logic here */
@@ -942,7 +942,7 @@ static apex_attributes *parse_ial_from_text(const char *text) {
942
942
  memset(attrs, 0, sizeof(apex_attributes));
943
943
 
944
944
  char buffer[2048];
945
- if (content_len >= (int)sizeof(buffer)) content_len = (int)sizeof(buffer) - 1;
945
+ if (content_len >= sizeof(buffer)) content_len = sizeof(buffer) - 1;
946
946
  memcpy(buffer, ial_start, content_len);
947
947
  buffer[content_len] = '\0';
948
948
 
@@ -89,7 +89,7 @@ static bool is_bear_callout(cmark_node *blockquote, callout_type_t *type,
89
89
  if (!type_end) return false;
90
90
 
91
91
  /* Extract type first */
92
- int type_len = type_end - type_start;
92
+ int type_len = (int)(type_end - type_start);
93
93
  if (type_len <= 0) return false;
94
94
 
95
95
  *type = detect_callout_type(type_start, type_len);
@@ -10,6 +10,7 @@
10
10
  #include <ctype.h>
11
11
  #include <stdbool.h>
12
12
  #include <errno.h>
13
+ #include <limits.h>
13
14
 
14
15
  /* Citation placeholder prefix - we'll use a unique marker */
15
16
  #define CITATION_PLACEHOLDER_PREFIX "<!--CITE:"
@@ -25,6 +26,17 @@ static bool is_valid_citation_char(char c) {
25
26
  c == '?' || c == '<' || c == '>' || c == '~' || c == '/';
26
27
  }
27
28
 
29
+ static int apex_ptrdiff_to_int(ptrdiff_t v) {
30
+ if (v <= 0) return 0;
31
+ if (v > INT_MAX) return INT_MAX;
32
+ return (int)v;
33
+ }
34
+
35
+ static int apex_size_to_int(size_t v) {
36
+ if (v > (size_t)INT_MAX) return INT_MAX;
37
+ return (int)v;
38
+ }
39
+
28
40
  /**
29
41
  * Extract citation key from text starting at pos
30
42
  * Returns length of key, or 0 if not valid
@@ -114,7 +126,7 @@ static int parse_pandoc_citation(const char *text, int pos, int len,
114
126
  /* Check for @key (author-in-text, no brackets) */
115
127
  if (*p == '@') {
116
128
  p++;
117
- int key_len = extract_citation_key(text, p - text, len, &key);
129
+ int key_len = extract_citation_key(text, apex_ptrdiff_to_int(p - text), len, &key);
118
130
  if (key_len > 0 && key) {
119
131
  /* Check if there's a locator after: @key [p. 33] */
120
132
  const char *after_key = p + key_len;
@@ -140,7 +152,7 @@ static int parse_pandoc_citation(const char *text, int pos, int len,
140
152
  cite->locator = locator;
141
153
  *citation_out = cite;
142
154
  free(key);
143
- return after_key - (text + pos);
155
+ return apex_ptrdiff_to_int(after_key - (text + pos));
144
156
  }
145
157
  free(key);
146
158
  free(locator);
@@ -164,7 +176,7 @@ static int parse_pandoc_citation(const char *text, int pos, int len,
164
176
  }
165
177
 
166
178
  /* Extract key */
167
- int key_len = extract_citation_key(text, p - text, len, &key);
179
+ int key_len = extract_citation_key(text, apex_ptrdiff_to_int(p - text), len, &key);
168
180
  if (key_len == 0 || !key) {
169
181
  return 0;
170
182
  }
@@ -253,7 +265,7 @@ static int parse_pandoc_citation(const char *text, int pos, int len,
253
265
  cite->suffix = suffix;
254
266
  *citation_out = cite;
255
267
  free(key);
256
- return p - (text + pos);
268
+ return apex_ptrdiff_to_int(p - (text + pos));
257
269
  }
258
270
 
259
271
  free(key);
@@ -280,7 +292,7 @@ static int parse_mmd_citation(const char *text, int pos, int len,
280
292
 
281
293
  /* Extract key */
282
294
  char *key = NULL;
283
- int key_len = extract_citation_key(text, p - text, len, &key);
295
+ int key_len = extract_citation_key(text, apex_ptrdiff_to_int(p - text), len, &key);
284
296
  if (key_len == 0 || !key) {
285
297
  return 0;
286
298
  }
@@ -317,7 +329,7 @@ static int parse_mmd_citation(const char *text, int pos, int len,
317
329
  cite->locator = locator;
318
330
  *citation_out = cite;
319
331
  free(key);
320
- return p - (text + pos);
332
+ return apex_ptrdiff_to_int(p - (text + pos));
321
333
  }
322
334
 
323
335
  free(key);
@@ -352,7 +364,7 @@ static int parse_mmark_citation(const char *text, int pos, int len,
352
364
  }
353
365
 
354
366
  /* Check if it's an RFC/BCP/STD/I-D/W3C pattern */
355
- if (!is_mmark_pattern(text, p - text, len)) {
367
+ if (!is_mmark_pattern(text, apex_ptrdiff_to_int(p - text), len)) {
356
368
  return 0; /* Not mmark pattern, might be Pandoc */
357
369
  }
358
370
 
@@ -390,7 +402,7 @@ static int parse_mmark_citation(const char *text, int pos, int len,
390
402
  cite->author_suppressed = author_suppressed;
391
403
  *citation_out = cite;
392
404
  free(key);
393
- return p - (text + pos);
405
+ return apex_ptrdiff_to_int(p - (text + pos));
394
406
  }
395
407
 
396
408
  free(key);
@@ -470,23 +482,23 @@ char *apex_process_citations(const char *text, apex_citation_registry *registry,
470
482
  continue;
471
483
  }
472
484
 
473
- int pos = read - text;
485
+ int pos = apex_ptrdiff_to_int(read - text);
474
486
  apex_citation *citation = NULL;
475
487
  int consumed = 0;
476
488
 
477
489
  /* Try mmark first (most specific pattern) */
478
490
  if (options->mode == APEX_MODE_UNIFIED) {
479
- consumed = parse_mmark_citation(text, pos, len, &citation, options);
491
+ consumed = parse_mmark_citation(text, pos, apex_size_to_int(len), &citation, options);
480
492
  }
481
493
 
482
494
  /* Try MultiMarkdown */
483
495
  if (!citation && (options->mode == APEX_MODE_MULTIMARKDOWN || options->mode == APEX_MODE_UNIFIED)) {
484
- consumed = parse_mmd_citation(text, pos, len, &citation, options);
496
+ consumed = parse_mmd_citation(text, pos, apex_size_to_int(len), &citation, options);
485
497
  }
486
498
 
487
499
  /* Try Pandoc (most common) */
488
500
  if (!citation && (options->mode == APEX_MODE_UNIFIED)) {
489
- consumed = parse_pandoc_citation(text, pos, len, &citation, options);
501
+ consumed = parse_pandoc_citation(text, pos, apex_size_to_int(len), &citation, options);
490
502
  }
491
503
 
492
504
  if (citation && consumed > 0) {