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