man.c 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <assert.h>
  5. #include "config.h"
  6. #include "cmark.h"
  7. #include "node.h"
  8. #include "buffer.h"
  9. #include "utf8.h"
  10. #include "render.h"
  11. #define OUT(s, wrap, escaping) renderer->out(renderer, s, wrap, escaping)
  12. #define LIT(s) renderer->out(renderer, s, false, LITERAL)
  13. #define CR() renderer->cr(renderer)
  14. #define BLANKLINE() renderer->blankline(renderer)
  15. #define LIST_NUMBER_SIZE 20
  16. // Functions to convert cmark_nodes to groff man strings.
  17. static void S_outc(cmark_renderer *renderer, cmark_escaping escape, int32_t c,
  18. unsigned char nextc) {
  19. (void)(nextc);
  20. if (escape == LITERAL) {
  21. cmark_render_code_point(renderer, c);
  22. return;
  23. }
  24. switch (c) {
  25. case 46:
  26. if (renderer->begin_line) {
  27. cmark_render_ascii(renderer, "\\&.");
  28. } else {
  29. cmark_render_code_point(renderer, c);
  30. }
  31. break;
  32. case 39:
  33. if (renderer->begin_line) {
  34. cmark_render_ascii(renderer, "\\&'");
  35. } else {
  36. cmark_render_code_point(renderer, c);
  37. }
  38. break;
  39. case 45:
  40. cmark_render_ascii(renderer, "\\-");
  41. break;
  42. case 92:
  43. cmark_render_ascii(renderer, "\\e");
  44. break;
  45. case 8216: // left single quote
  46. cmark_render_ascii(renderer, "\\[oq]");
  47. break;
  48. case 8217: // right single quote
  49. cmark_render_ascii(renderer, "\\[cq]");
  50. break;
  51. case 8220: // left double quote
  52. cmark_render_ascii(renderer, "\\[lq]");
  53. break;
  54. case 8221: // right double quote
  55. cmark_render_ascii(renderer, "\\[rq]");
  56. break;
  57. case 8212: // em dash
  58. cmark_render_ascii(renderer, "\\[em]");
  59. break;
  60. case 8211: // en dash
  61. cmark_render_ascii(renderer, "\\[en]");
  62. break;
  63. default:
  64. cmark_render_code_point(renderer, c);
  65. }
  66. }
  67. static int S_render_node(cmark_renderer *renderer, cmark_node *node,
  68. cmark_event_type ev_type, int options) {
  69. cmark_node *tmp;
  70. int list_number;
  71. bool entering = (ev_type == CMARK_EVENT_ENTER);
  72. bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options);
  73. // avoid unused parameter error:
  74. (void)(options);
  75. switch (node->type) {
  76. case CMARK_NODE_DOCUMENT:
  77. break;
  78. case CMARK_NODE_BLOCK_QUOTE:
  79. if (entering) {
  80. CR();
  81. LIT(".RS");
  82. CR();
  83. } else {
  84. CR();
  85. LIT(".RE");
  86. CR();
  87. }
  88. break;
  89. case CMARK_NODE_LIST:
  90. break;
  91. case CMARK_NODE_ITEM:
  92. if (entering) {
  93. CR();
  94. LIT(".IP ");
  95. if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) {
  96. LIT("\\[bu] 2");
  97. } else {
  98. list_number = cmark_node_get_list_start(node->parent);
  99. tmp = node;
  100. while (tmp->prev) {
  101. tmp = tmp->prev;
  102. list_number += 1;
  103. }
  104. char list_number_s[LIST_NUMBER_SIZE];
  105. snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number);
  106. LIT(list_number_s);
  107. }
  108. CR();
  109. } else {
  110. CR();
  111. }
  112. break;
  113. case CMARK_NODE_HEADING:
  114. if (entering) {
  115. CR();
  116. LIT(cmark_node_get_heading_level(node) == 1 ? ".SH" : ".SS");
  117. CR();
  118. } else {
  119. CR();
  120. }
  121. break;
  122. case CMARK_NODE_CODE_BLOCK:
  123. CR();
  124. LIT(".IP\n.nf\n\\f[C]\n");
  125. OUT(cmark_node_get_literal(node), false, NORMAL);
  126. CR();
  127. LIT("\\f[]\n.fi");
  128. CR();
  129. break;
  130. case CMARK_NODE_HTML_BLOCK:
  131. break;
  132. case CMARK_NODE_CUSTOM_BLOCK:
  133. CR();
  134. OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node),
  135. false, LITERAL);
  136. CR();
  137. break;
  138. case CMARK_NODE_THEMATIC_BREAK:
  139. CR();
  140. LIT(".PP\n * * * * *");
  141. CR();
  142. break;
  143. case CMARK_NODE_PARAGRAPH:
  144. if (entering) {
  145. // no blank line if first paragraph in list:
  146. if (node->parent && node->parent->type == CMARK_NODE_ITEM &&
  147. node->prev == NULL) {
  148. // no blank line or .PP
  149. } else {
  150. CR();
  151. LIT(".PP");
  152. CR();
  153. }
  154. } else {
  155. CR();
  156. }
  157. break;
  158. case CMARK_NODE_TEXT:
  159. OUT(cmark_node_get_literal(node), allow_wrap, NORMAL);
  160. break;
  161. case CMARK_NODE_LINEBREAK:
  162. LIT(".PD 0\n.P\n.PD");
  163. CR();
  164. break;
  165. case CMARK_NODE_SOFTBREAK:
  166. if (options & CMARK_OPT_HARDBREAKS) {
  167. LIT(".PD 0\n.P\n.PD");
  168. CR();
  169. } else if (renderer->width == 0 && !(CMARK_OPT_NOBREAKS & options)) {
  170. CR();
  171. } else {
  172. OUT(" ", allow_wrap, LITERAL);
  173. }
  174. break;
  175. case CMARK_NODE_CODE:
  176. LIT("\\f[C]");
  177. OUT(cmark_node_get_literal(node), allow_wrap, NORMAL);
  178. LIT("\\f[]");
  179. break;
  180. case CMARK_NODE_HTML_INLINE:
  181. break;
  182. case CMARK_NODE_CUSTOM_INLINE:
  183. OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node),
  184. false, LITERAL);
  185. break;
  186. case CMARK_NODE_STRONG:
  187. if (entering) {
  188. LIT("\\f[B]");
  189. } else {
  190. LIT("\\f[]");
  191. }
  192. break;
  193. case CMARK_NODE_EMPH:
  194. if (entering) {
  195. LIT("\\f[I]");
  196. } else {
  197. LIT("\\f[]");
  198. }
  199. break;
  200. case CMARK_NODE_LINK:
  201. if (!entering) {
  202. LIT(" (");
  203. OUT(cmark_node_get_url(node), allow_wrap, URL);
  204. LIT(")");
  205. }
  206. break;
  207. case CMARK_NODE_IMAGE:
  208. if (entering) {
  209. LIT("[IMAGE: ");
  210. } else {
  211. LIT("]");
  212. }
  213. break;
  214. default:
  215. assert(false);
  216. break;
  217. }
  218. return 1;
  219. }
  220. char *cmark_render_man(cmark_node *root, int options, int width) {
  221. return cmark_render(root, options, width, S_outc, S_render_node);
  222. }