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. |
20 | static 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 | |
73 | static 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 | |
250 | char *cmark_render_man(cmark_node *root, int options, int width) { |
251 | return cmark_render(root, options, width, S_outc, S_render_node); |
252 | } |
253 | |