xref: /freebsd/contrib/mandoc/mdoc_markdown.c (revision 18a870751b036f1dc78b36084ccb993d139a11bb)
1 /* $Id: mdoc_markdown.c,v 1.40 2025/06/26 17:06:34 schwarze Exp $ */
2 /*
3  * Copyright (c) 2017, 2018, 2020, 2025 Ingo Schwarze <schwarze@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  *
17  * Markdown formatter for mdoc(7) used by mandoc(1).
18  */
19 #include "config.h"
20 
21 #include <sys/types.h>
22 
23 #include <assert.h>
24 #include <ctype.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include "mandoc_aux.h"
30 #include "mandoc.h"
31 #include "roff.h"
32 #include "mdoc.h"
33 #include "main.h"
34 
35 struct	md_act {
36 	int		(*cond)(struct roff_node *);
37 	int		(*pre)(struct roff_node *);
38 	void		(*post)(struct roff_node *);
39 	const char	 *prefix; /* pre-node string constant */
40 	const char	 *suffix; /* post-node string constant */
41 };
42 
43 static	void	 md_nodelist(struct roff_node *);
44 static	void	 md_node(struct roff_node *);
45 static	const char *md_stack(char);
46 static	void	 md_preword(void);
47 static	void	 md_rawword(const char *);
48 static	void	 md_word(const char *);
49 static	void	 md_named(const char *);
50 static	void	 md_char(unsigned char);
51 static	void	 md_uri(const char *);
52 
53 static	int	 md_cond_head(struct roff_node *);
54 static	int	 md_cond_body(struct roff_node *);
55 
56 static	int	 md_pre_abort(struct roff_node *);
57 static	int	 md_pre_raw(struct roff_node *);
58 static	int	 md_pre_word(struct roff_node *);
59 static	int	 md_pre_skip(struct roff_node *);
60 static	void	 md_pre_syn(struct roff_node *);
61 static	int	 md_pre_An(struct roff_node *);
62 static	int	 md_pre_Ap(struct roff_node *);
63 static	int	 md_pre_Bd(struct roff_node *);
64 static	int	 md_pre_Bk(struct roff_node *);
65 static	int	 md_pre_Bl(struct roff_node *);
66 static	int	 md_pre_D1(struct roff_node *);
67 static	int	 md_pre_Dl(struct roff_node *);
68 static	int	 md_pre_En(struct roff_node *);
69 static	int	 md_pre_Eo(struct roff_node *);
70 static	int	 md_pre_Fa(struct roff_node *);
71 static	int	 md_pre_Fd(struct roff_node *);
72 static	int	 md_pre_Fn(struct roff_node *);
73 static	int	 md_pre_Fo(struct roff_node *);
74 static	int	 md_pre_In(struct roff_node *);
75 static	int	 md_pre_It(struct roff_node *);
76 static	int	 md_pre_Lk(struct roff_node *);
77 static	int	 md_pre_Mt(struct roff_node *);
78 static	int	 md_pre_Nd(struct roff_node *);
79 static	int	 md_pre_Nm(struct roff_node *);
80 static	int	 md_pre_No(struct roff_node *);
81 static	int	 md_pre_Ns(struct roff_node *);
82 static	int	 md_pre_Pp(struct roff_node *);
83 static	int	 md_pre_Rs(struct roff_node *);
84 static	int	 md_pre_Sh(struct roff_node *);
85 static	int	 md_pre_Sm(struct roff_node *);
86 static	int	 md_pre_Vt(struct roff_node *);
87 static	int	 md_pre_Xr(struct roff_node *);
88 static	int	 md_pre__R(struct roff_node *);
89 static	int	 md_pre__T(struct roff_node *);
90 static	int	 md_pre_br(struct roff_node *);
91 
92 static	void	 md_post_raw(struct roff_node *);
93 static	void	 md_post_word(struct roff_node *);
94 static	void	 md_post_pc(struct roff_node *);
95 static	void	 md_post_Bk(struct roff_node *);
96 static	void	 md_post_Bl(struct roff_node *);
97 static	void	 md_post_D1(struct roff_node *);
98 static	void	 md_post_En(struct roff_node *);
99 static	void	 md_post_Eo(struct roff_node *);
100 static	void	 md_post_Fa(struct roff_node *);
101 static	void	 md_post_Fd(struct roff_node *);
102 static	void	 md_post_Fl(struct roff_node *);
103 static	void	 md_post_Fn(struct roff_node *);
104 static	void	 md_post_Fo(struct roff_node *);
105 static	void	 md_post_In(struct roff_node *);
106 static	void	 md_post_It(struct roff_node *);
107 static	void	 md_post_Lb(struct roff_node *);
108 static	void	 md_post_Nm(struct roff_node *);
109 static	void	 md_post_Pf(struct roff_node *);
110 static	void	 md_post_Vt(struct roff_node *);
111 static	void	 md_post__T(struct roff_node *);
112 
113 static	const struct md_act md_acts[MDOC_MAX - MDOC_Dd] = {
114 	{ NULL, NULL, NULL, NULL, NULL }, /* Dd */
115 	{ NULL, NULL, NULL, NULL, NULL }, /* Dt */
116 	{ NULL, NULL, NULL, NULL, NULL }, /* Os */
117 	{ NULL, md_pre_Sh, NULL, NULL, NULL }, /* Sh */
118 	{ NULL, md_pre_Sh, NULL, NULL, NULL }, /* Ss */
119 	{ NULL, md_pre_Pp, NULL, NULL, NULL }, /* Pp */
120 	{ md_cond_body, md_pre_D1, md_post_D1, NULL, NULL }, /* D1 */
121 	{ md_cond_body, md_pre_Dl, md_post_D1, NULL, NULL }, /* Dl */
122 	{ md_cond_body, md_pre_Bd, md_post_D1, NULL, NULL }, /* Bd */
123 	{ NULL, NULL, NULL, NULL, NULL }, /* Ed */
124 	{ md_cond_body, md_pre_Bl, md_post_Bl, NULL, NULL }, /* Bl */
125 	{ NULL, NULL, NULL, NULL, NULL }, /* El */
126 	{ NULL, md_pre_It, md_post_It, NULL, NULL }, /* It */
127 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ad */
128 	{ NULL, md_pre_An, NULL, NULL, NULL }, /* An */
129 	{ NULL, md_pre_Ap, NULL, NULL, NULL }, /* Ap */
130 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ar */
131 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cd */
132 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cm */
133 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Dv */
134 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Er */
135 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Ev */
136 	{ NULL, NULL, NULL, NULL, NULL }, /* Ex */
137 	{ NULL, md_pre_Fa, md_post_Fa, NULL, NULL }, /* Fa */
138 	{ NULL, md_pre_Fd, md_post_Fd, "**", "**" }, /* Fd */
139 	{ NULL, md_pre_raw, md_post_Fl, "**-", "**" }, /* Fl */
140 	{ NULL, md_pre_Fn, md_post_Fn, NULL, NULL }, /* Fn */
141 	{ NULL, md_pre_Fd, md_post_raw, "*", "*" }, /* Ft */
142 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ic */
143 	{ NULL, md_pre_In, md_post_In, NULL, NULL }, /* In */
144 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Li */
145 	{ md_cond_head, md_pre_Nd, NULL, NULL, NULL }, /* Nd */
146 	{ NULL, md_pre_Nm, md_post_Nm, "**", "**" }, /* Nm */
147 	{ md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Op */
148 	{ NULL, md_pre_abort, NULL, NULL, NULL }, /* Ot */
149 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Pa */
150 	{ NULL, NULL, NULL, NULL, NULL }, /* Rv */
151 	{ NULL, NULL, NULL, NULL, NULL }, /* St */
152 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Va */
153 	{ NULL, md_pre_Vt, md_post_Vt, "*", "*" }, /* Vt */
154 	{ NULL, md_pre_Xr, NULL, NULL, NULL }, /* Xr */
155 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %A */
156 	{ NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %B */
157 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %D */
158 	{ NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %I */
159 	{ NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %J */
160 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %N */
161 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %O */
162 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %P */
163 	{ NULL, md_pre__R, md_post_pc, NULL, NULL }, /* %R */
164 	{ NULL, md_pre__T, md_post__T, NULL, NULL }, /* %T */
165 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %V */
166 	{ NULL, NULL, NULL, NULL, NULL }, /* Ac */
167 	{ md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Ao */
168 	{ md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Aq */
169 	{ NULL, NULL, NULL, NULL, NULL }, /* At */
170 	{ NULL, NULL, NULL, NULL, NULL }, /* Bc */
171 	{ NULL, NULL, NULL, NULL, NULL }, /* Bf XXX not implemented */
172 	{ md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bo */
173 	{ md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bq */
174 	{ NULL, NULL, NULL, NULL, NULL }, /* Bsx */
175 	{ NULL, NULL, NULL, NULL, NULL }, /* Bx */
176 	{ NULL, NULL, NULL, NULL, NULL }, /* Db */
177 	{ NULL, NULL, NULL, NULL, NULL }, /* Dc */
178 	{ md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Do */
179 	{ md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Dq */
180 	{ NULL, NULL, NULL, NULL, NULL }, /* Ec */
181 	{ NULL, NULL, NULL, NULL, NULL }, /* Ef */
182 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Em */
183 	{ md_cond_body, md_pre_Eo, md_post_Eo, NULL, NULL }, /* Eo */
184 	{ NULL, NULL, NULL, NULL, NULL }, /* Fx */
185 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ms */
186 	{ NULL, md_pre_No, NULL, NULL, NULL }, /* No */
187 	{ NULL, md_pre_Ns, NULL, NULL, NULL }, /* Ns */
188 	{ NULL, NULL, NULL, NULL, NULL }, /* Nx */
189 	{ NULL, NULL, NULL, NULL, NULL }, /* Ox */
190 	{ NULL, NULL, NULL, NULL, NULL }, /* Pc */
191 	{ NULL, NULL, md_post_Pf, NULL, NULL }, /* Pf */
192 	{ md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Po */
193 	{ md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Pq */
194 	{ NULL, NULL, NULL, NULL, NULL }, /* Qc */
195 	{ md_cond_body, md_pre_raw, md_post_raw, "'`", "`'" }, /* Ql */
196 	{ md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qo */
197 	{ md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qq */
198 	{ NULL, NULL, NULL, NULL, NULL }, /* Re */
199 	{ md_cond_body, md_pre_Rs, NULL, NULL, NULL }, /* Rs */
200 	{ NULL, NULL, NULL, NULL, NULL }, /* Sc */
201 	{ md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* So */
202 	{ md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* Sq */
203 	{ NULL, md_pre_Sm, NULL, NULL, NULL }, /* Sm */
204 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Sx */
205 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Sy */
206 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Tn */
207 	{ NULL, NULL, NULL, NULL, NULL }, /* Ux */
208 	{ NULL, NULL, NULL, NULL, NULL }, /* Xc */
209 	{ NULL, NULL, NULL, NULL, NULL }, /* Xo */
210 	{ NULL, md_pre_Fo, md_post_Fo, "**", "**" }, /* Fo */
211 	{ NULL, NULL, NULL, NULL, NULL }, /* Fc */
212 	{ md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Oo */
213 	{ NULL, NULL, NULL, NULL, NULL }, /* Oc */
214 	{ NULL, md_pre_Bk, md_post_Bk, NULL, NULL }, /* Bk */
215 	{ NULL, NULL, NULL, NULL, NULL }, /* Ek */
216 	{ NULL, NULL, NULL, NULL, NULL }, /* Bt */
217 	{ NULL, NULL, NULL, NULL, NULL }, /* Hf */
218 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Fr */
219 	{ NULL, NULL, NULL, NULL, NULL }, /* Ud */
220 	{ NULL, NULL, md_post_Lb, NULL, NULL }, /* Lb */
221 	{ NULL, md_pre_abort, NULL, NULL, NULL }, /* Lp */
222 	{ NULL, md_pre_Lk, NULL, NULL, NULL }, /* Lk */
223 	{ NULL, md_pre_Mt, NULL, NULL, NULL }, /* Mt */
224 	{ md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Brq */
225 	{ md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Bro */
226 	{ NULL, NULL, NULL, NULL, NULL }, /* Brc */
227 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %C */
228 	{ NULL, md_pre_skip, NULL, NULL, NULL }, /* Es */
229 	{ md_cond_body, md_pre_En, md_post_En, NULL, NULL }, /* En */
230 	{ NULL, NULL, NULL, NULL, NULL }, /* Dx */
231 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %Q */
232 	{ NULL, md_pre_Lk, md_post_pc, NULL, NULL }, /* %U */
233 	{ NULL, NULL, NULL, NULL, NULL }, /* Ta */
234 	{ NULL, md_pre_skip, NULL, NULL, NULL }, /* Tg */
235 };
236 static const struct md_act *md_act(enum roff_tok);
237 
238 static	int	 outflags;
239 #define	MD_spc		 (1 << 0)  /* Blank character before next word. */
240 #define	MD_spc_force	 (1 << 1)  /* Even before trailing punctuation. */
241 #define	MD_nonl		 (1 << 2)  /* Prevent linebreak in markdown code. */
242 #define	MD_nl		 (1 << 3)  /* Break markdown code line. */
243 #define	MD_br		 (1 << 4)  /* Insert an output line break. */
244 #define	MD_sp		 (1 << 5)  /* Insert a paragraph break. */
245 #define	MD_Sm		 (1 << 6)  /* Horizontal spacing mode. */
246 #define	MD_Bk		 (1 << 7)  /* Word keep mode. */
247 #define	MD_An_split	 (1 << 8)  /* Author mode is "split". */
248 #define	MD_An_nosplit	 (1 << 9)  /* Author mode is "nosplit". */
249 
250 static	int	 escflags; /* Escape in generated markdown code: */
251 #define	ESC_BOL	 (1 << 0)  /* "#*+-" near the beginning of a line. */
252 #define	ESC_NUM	 (1 << 1)  /* "." after a leading number. */
253 #define	ESC_HYP	 (1 << 2)  /* "(" immediately after "]". */
254 #define	ESC_SQU	 (1 << 4)  /* "]" when "[" is open. */
255 #define	ESC_FON	 (1 << 5)  /* "*" immediately after unrelated "*". */
256 #define	ESC_EOL	 (1 << 6)  /* " " at the and of a line. */
257 
258 static	int	 code_blocks, quote_blocks, list_blocks;
259 static	int	 outcount;
260 
261 
262 static const struct md_act *
263 md_act(enum roff_tok tok)
264 {
265 	assert(tok >= MDOC_Dd && tok <= MDOC_MAX);
266 	return md_acts + (tok - MDOC_Dd);
267 }
268 
269 void
270 markdown_mdoc(void *arg, const struct roff_meta *mdoc)
271 {
272 	outflags = MD_Sm;
273 	md_word(mdoc->title);
274 	if (mdoc->msec != NULL) {
275 		outflags &= ~MD_spc;
276 		md_word("(");
277 		md_word(mdoc->msec);
278 		md_word(")");
279 	}
280 	md_word("-");
281 	md_word(mdoc->vol);
282 	if (mdoc->arch != NULL) {
283 		md_word("(");
284 		md_word(mdoc->arch);
285 		md_word(")");
286 	}
287 	outflags |= MD_sp;
288 
289 	md_nodelist(mdoc->first->child);
290 
291 	outflags |= MD_sp;
292 	md_word(mdoc->os);
293 	md_word("-");
294 	md_word(mdoc->date);
295 	md_word("-");
296 	md_word(mdoc->title);
297 	if (mdoc->msec != NULL) {
298 		outflags &= ~MD_spc;
299 		md_word("(");
300 		md_word(mdoc->msec);
301 		md_word(")");
302 	}
303 	putchar('\n');
304 }
305 
306 static void
307 md_nodelist(struct roff_node *n)
308 {
309 	while (n != NULL) {
310 		md_node(n);
311 		n = n->next;
312 	}
313 }
314 
315 static void
316 md_node(struct roff_node *n)
317 {
318 	const struct md_act	*act;
319 	int			 cond, process_children;
320 
321 	if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
322 		return;
323 
324 	if (outflags & MD_nonl)
325 		outflags &= ~(MD_nl | MD_sp);
326 	else if (outflags & MD_spc &&
327 	     n->flags & NODE_LINE &&
328 	     !roff_node_transparent(n))
329 		outflags |= MD_nl;
330 
331 	act = NULL;
332 	cond = 0;
333 	process_children = 1;
334 	n->flags &= ~NODE_ENDED;
335 
336 	if (n->type == ROFFT_TEXT) {
337 		if (n->flags & NODE_DELIMC)
338 			outflags &= ~(MD_spc | MD_spc_force);
339 		else if (outflags & MD_Sm)
340 			outflags |= MD_spc_force;
341 		md_word(n->string);
342 		if (n->flags & NODE_DELIMO)
343 			outflags &= ~(MD_spc | MD_spc_force);
344 		else if (outflags & MD_Sm)
345 			outflags |= MD_spc;
346 	} else if (n->tok < ROFF_MAX) {
347 		switch (n->tok) {
348 		case ROFF_br:
349 			process_children = md_pre_br(n);
350 			break;
351 		case ROFF_sp:
352 			process_children = md_pre_Pp(n);
353 			break;
354 		default:
355 			process_children = 0;
356 			break;
357 		}
358 	} else {
359 		act = md_act(n->tok);
360 		cond = act->cond == NULL || (*act->cond)(n);
361 		if (cond && act->pre != NULL &&
362 		    (n->end == ENDBODY_NOT || n->child != NULL))
363 			process_children = (*act->pre)(n);
364 	}
365 
366 	if (process_children && n->child != NULL)
367 		md_nodelist(n->child);
368 
369 	if (n->flags & NODE_ENDED)
370 		return;
371 
372 	if (cond && act->post != NULL)
373 		(*act->post)(n);
374 
375 	if (n->end != ENDBODY_NOT)
376 		n->body->flags |= NODE_ENDED;
377 }
378 
379 static const char *
380 md_stack(char c)
381 {
382 	static char	*stack;
383 	static size_t	 sz;
384 	static size_t	 cur;
385 
386 	switch (c) {
387 	case '\0':
388 		break;
389 	case (char)-1:
390 		assert(cur);
391 		stack[--cur] = '\0';
392 		break;
393 	default:
394 		if (cur + 1 >= sz) {
395 			sz += 8;
396 			stack = mandoc_realloc(stack, sz);
397 		}
398 		stack[cur] = c;
399 		stack[++cur] = '\0';
400 		break;
401 	}
402 	return stack == NULL ? "" : stack;
403 }
404 
405 /*
406  * Handle vertical and horizontal spacing.
407  */
408 static void
409 md_preword(void)
410 {
411 	const char	*cp;
412 
413 	/*
414 	 * If a list block is nested inside a code block or a blockquote,
415 	 * blank lines for paragraph breaks no longer work; instead,
416 	 * they terminate the list.  Work around this markdown issue
417 	 * by using mere line breaks instead.
418 	 */
419 
420 	if (list_blocks && outflags & MD_sp) {
421 		outflags &= ~MD_sp;
422 		outflags |= MD_br;
423 	}
424 
425 	/*
426 	 * End the old line if requested.
427 	 * Escape whitespace at the end of the markdown line
428 	 * such that it won't look like an output line break.
429 	 */
430 
431 	if (outflags & MD_sp)
432 		putchar('\n');
433 	else if (outflags & MD_br) {
434 		putchar(' ');
435 		putchar(' ');
436 	} else if (outflags & MD_nl && escflags & ESC_EOL)
437 		md_named("zwnj");
438 
439 	/* Start a new line if necessary. */
440 
441 	if (outflags & (MD_nl | MD_br | MD_sp)) {
442 		putchar('\n');
443 		for (cp = md_stack('\0'); *cp != '\0'; cp++) {
444 			putchar(*cp);
445 			if (*cp == '>')
446 				putchar(' ');
447 		}
448 		outflags &= ~(MD_nl | MD_br | MD_sp);
449 		escflags = ESC_BOL;
450 		outcount = 0;
451 
452 	/* Handle horizontal spacing. */
453 
454 	} else if (outflags & MD_spc) {
455 		if (outflags & MD_Bk)
456 			fputs("&nbsp;", stdout);
457 		else
458 			putchar(' ');
459 		escflags &= ~ESC_FON;
460 		outcount++;
461 	}
462 
463 	outflags &= ~(MD_spc_force | MD_nonl);
464 	if (outflags & MD_Sm)
465 		outflags |= MD_spc;
466 	else
467 		outflags &= ~MD_spc;
468 }
469 
470 /*
471  * Print markdown syntax elements.
472  * Can also be used for constant strings when neither escaping
473  * nor delimiter handling is required.
474  */
475 static void
476 md_rawword(const char *s)
477 {
478 	md_preword();
479 
480 	if (*s == '\0')
481 		return;
482 
483 	if (escflags & ESC_FON) {
484 		escflags &= ~ESC_FON;
485 		if (*s == '*' && !code_blocks)
486 			fputs("&zwnj;", stdout);
487 	}
488 
489 	while (*s != '\0') {
490 		switch(*s) {
491 		case '*':
492 			if (s[1] == '\0')
493 				escflags |= ESC_FON;
494 			break;
495 		case '[':
496 			escflags |= ESC_SQU;
497 			break;
498 		case ']':
499 			escflags |= ESC_HYP;
500 			escflags &= ~ESC_SQU;
501 			break;
502 		default:
503 			break;
504 		}
505 		md_char(*s++);
506 	}
507 	if (s[-1] == ' ')
508 		escflags |= ESC_EOL;
509 	else
510 		escflags &= ~ESC_EOL;
511 }
512 
513 /*
514  * Print text and mdoc(7) syntax elements.
515  */
516 static void
517 md_word(const char *s)
518 {
519 	const char	*seq, *prevfont, *currfont, *nextfont;
520 	char		 c;
521 	int		 bs, sz, uc, breakline;
522 
523 	/* No spacing before closing delimiters. */
524 	if (s[0] != '\0' && s[1] == '\0' &&
525 	    strchr("!),.:;?]", s[0]) != NULL &&
526 	    (outflags & MD_spc_force) == 0)
527 		outflags &= ~MD_spc;
528 
529 	md_preword();
530 
531 	if (*s == '\0')
532 		return;
533 
534 	/* No spacing after opening delimiters. */
535 	if ((s[0] == '(' || s[0] == '[') && s[1] == '\0')
536 		outflags &= ~MD_spc;
537 
538 	breakline = 0;
539 	prevfont = currfont = "";
540 	while ((c = *s++) != '\0') {
541 		bs = 0;
542 		switch(c) {
543 		case ASCII_NBRSP:
544 			if (code_blocks)
545 				c = ' ';
546 			else {
547 				md_named("nbsp");
548 				c = '\0';
549 			}
550 			break;
551 		case ASCII_HYPH:
552 			bs = escflags & ESC_BOL && !code_blocks;
553 			c = '-';
554 			break;
555 		case ASCII_BREAK:
556 			continue;
557 		case '#':
558 		case '+':
559 		case '-':
560 			bs = escflags & ESC_BOL && !code_blocks;
561 			break;
562 		case '(':
563 			bs = escflags & ESC_HYP && !code_blocks;
564 			break;
565 		case ')':
566 			bs = escflags & ESC_NUM && !code_blocks;
567 			break;
568 		case '*':
569 		case '[':
570 		case '_':
571 		case '`':
572 			bs = !code_blocks;
573 			break;
574 		case '.':
575 			bs = escflags & ESC_NUM && !code_blocks;
576 			break;
577 		case '<':
578 			if (code_blocks == 0) {
579 				md_named("lt");
580 				c = '\0';
581 			}
582 			break;
583 		case '=':
584 			if (escflags & ESC_BOL && !code_blocks) {
585 				md_named("equals");
586 				c = '\0';
587 			}
588 			break;
589 		case '>':
590 			if (code_blocks == 0) {
591 				md_named("gt");
592 				c = '\0';
593 			}
594 			break;
595 		case '\\':
596 			uc = 0;
597 			nextfont = NULL;
598 			switch (mandoc_escape(&s, &seq, &sz)) {
599 			case ESCAPE_UNICODE:
600 				uc = mchars_num2uc(seq + 1, sz - 1);
601 				break;
602 			case ESCAPE_NUMBERED:
603 				uc = mchars_num2char(seq, sz);
604 				break;
605 			case ESCAPE_SPECIAL:
606 				uc = mchars_spec2cp(seq, sz);
607 				break;
608 			case ESCAPE_UNDEF:
609 				uc = *seq;
610 				break;
611 			case ESCAPE_DEVICE:
612 				md_rawword("markdown");
613 				continue;
614 			case ESCAPE_FONTBOLD:
615 			case ESCAPE_FONTCB:
616 				nextfont = "**";
617 				break;
618 			case ESCAPE_FONTITALIC:
619 			case ESCAPE_FONTCI:
620 				nextfont = "*";
621 				break;
622 			case ESCAPE_FONTBI:
623 				nextfont = "***";
624 				break;
625 			case ESCAPE_FONT:
626 			case ESCAPE_FONTCR:
627 			case ESCAPE_FONTROMAN:
628 				nextfont = "";
629 				break;
630 			case ESCAPE_FONTPREV:
631 				nextfont = prevfont;
632 				break;
633 			case ESCAPE_BREAK:
634 				breakline = 1;
635 				break;
636 			case ESCAPE_NOSPACE:
637 			case ESCAPE_SKIPCHAR:
638 			case ESCAPE_OVERSTRIKE:
639 				/* XXX not implemented */
640 				/* FALLTHROUGH */
641 			case ESCAPE_ERROR:
642 			default:
643 				break;
644 			}
645 			if (nextfont != NULL && !code_blocks) {
646 				if (*currfont != '\0') {
647 					outflags &= ~MD_spc;
648 					md_rawword(currfont);
649 				}
650 				prevfont = currfont;
651 				currfont = nextfont;
652 				if (*currfont != '\0') {
653 					outflags &= ~MD_spc;
654 					md_rawword(currfont);
655 				}
656 			}
657 			if (uc) {
658 				if ((uc < 0x20 && uc != 0x09) ||
659 				    (uc > 0x7E && uc < 0xA0))
660 					uc = 0xFFFD;
661 				if (code_blocks) {
662 					seq = mchars_uc2str(uc);
663 					fputs(seq, stdout);
664 					outcount += strlen(seq);
665 				} else {
666 					printf("&#%d;", uc);
667 					outcount++;
668 				}
669 				escflags &= ~ESC_FON;
670 			}
671 			c = '\0';
672 			break;
673 		case ']':
674 			bs = escflags & ESC_SQU && !code_blocks;
675 			escflags |= ESC_HYP;
676 			break;
677 		default:
678 			break;
679 		}
680 		if (bs)
681 			putchar('\\');
682 		md_char(c);
683 		if (breakline &&
684 		    (*s == '\0' || *s == ' ' || *s == ASCII_NBRSP)) {
685 			printf("  \n");
686 			breakline = 0;
687 			while (*s == ' ' || *s == ASCII_NBRSP)
688 				s++;
689 		}
690 	}
691 	if (*currfont != '\0') {
692 		outflags &= ~MD_spc;
693 		md_rawword(currfont);
694 	} else if (s[-2] == ' ')
695 		escflags |= ESC_EOL;
696 	else
697 		escflags &= ~ESC_EOL;
698 }
699 
700 /*
701  * Print a single HTML named character reference.
702  */
703 static void
704 md_named(const char *s)
705 {
706 	printf("&%s;", s);
707 	escflags &= ~(ESC_FON | ESC_EOL);
708 	outcount++;
709 }
710 
711 /*
712  * Print a single raw character and maintain certain escape flags.
713  */
714 static void
715 md_char(unsigned char c)
716 {
717 	if (c != '\0') {
718 		putchar(c);
719 		if (c == '*')
720 			escflags |= ESC_FON;
721 		else
722 			escflags &= ~ESC_FON;
723 		outcount++;
724 	}
725 	if (c != ']')
726 		escflags &= ~ESC_HYP;
727 	if (c == ' ' || c == '\t' || c == '>')
728 		return;
729 	if (isdigit(c) == 0)
730 		escflags &= ~ESC_NUM;
731 	else if (escflags & ESC_BOL)
732 		escflags |= ESC_NUM;
733 	escflags &= ~ESC_BOL;
734 }
735 
736 static int
737 md_cond_head(struct roff_node *n)
738 {
739 	return n->type == ROFFT_HEAD;
740 }
741 
742 static int
743 md_cond_body(struct roff_node *n)
744 {
745 	return n->type == ROFFT_BODY;
746 }
747 
748 static int
749 md_pre_abort(struct roff_node *n)
750 {
751 	abort();
752 }
753 
754 static int
755 md_pre_raw(struct roff_node *n)
756 {
757 	const char	*prefix;
758 
759 	if ((prefix = md_act(n->tok)->prefix) != NULL) {
760 		md_rawword(prefix);
761 		outflags &= ~MD_spc;
762 		if (strchr(prefix, '`') != NULL)
763 			code_blocks++;
764 	}
765 	return 1;
766 }
767 
768 static void
769 md_post_raw(struct roff_node *n)
770 {
771 	const char	*suffix;
772 
773 	if ((suffix = md_act(n->tok)->suffix) != NULL) {
774 		outflags &= ~(MD_spc | MD_nl);
775 		md_rawword(suffix);
776 		if (strchr(suffix, '`') != NULL)
777 			code_blocks--;
778 	}
779 }
780 
781 static int
782 md_pre_word(struct roff_node *n)
783 {
784 	const char	*prefix;
785 
786 	if ((prefix = md_act(n->tok)->prefix) != NULL) {
787 		md_word(prefix);
788 		outflags &= ~MD_spc;
789 	}
790 	return 1;
791 }
792 
793 static void
794 md_post_word(struct roff_node *n)
795 {
796 	const char	*suffix;
797 
798 	if ((suffix = md_act(n->tok)->suffix) != NULL) {
799 		outflags &= ~(MD_spc | MD_nl);
800 		md_word(suffix);
801 	}
802 }
803 
804 static void
805 md_post_pc(struct roff_node *n)
806 {
807 	struct roff_node *nn;
808 
809 	md_post_raw(n);
810 	if (n->parent->tok != MDOC_Rs)
811 		return;
812 
813 	if ((nn = roff_node_next(n)) != NULL) {
814 		md_word(",");
815 		if (nn->tok == n->tok &&
816 		    (nn = roff_node_prev(n)) != NULL &&
817 		    nn->tok == n->tok)
818 			md_word("and");
819 	} else {
820 		md_word(".");
821 		outflags |= MD_nl;
822 	}
823 }
824 
825 static int
826 md_pre_skip(struct roff_node *n)
827 {
828 	return 0;
829 }
830 
831 static void
832 md_pre_syn(struct roff_node *n)
833 {
834 	struct roff_node *np;
835 
836 	if ((n->flags & NODE_SYNPRETTY) == 0 ||
837 	    (np = roff_node_prev(n)) == NULL)
838 		return;
839 
840 	if (np->tok == n->tok &&
841 	    n->tok != MDOC_Ft &&
842 	    n->tok != MDOC_Fo &&
843 	    n->tok != MDOC_Fn) {
844 		outflags |= MD_br;
845 		return;
846 	}
847 
848 	switch (np->tok) {
849 	case MDOC_Fd:
850 	case MDOC_Fn:
851 	case MDOC_Fo:
852 	case MDOC_In:
853 	case MDOC_Vt:
854 		outflags |= MD_sp;
855 		break;
856 	case MDOC_Ft:
857 		if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) {
858 			outflags |= MD_sp;
859 			break;
860 		}
861 		/* FALLTHROUGH */
862 	default:
863 		outflags |= MD_br;
864 		break;
865 	}
866 }
867 
868 static int
869 md_pre_An(struct roff_node *n)
870 {
871 	switch (n->norm->An.auth) {
872 	case AUTH_split:
873 		outflags &= ~MD_An_nosplit;
874 		outflags |= MD_An_split;
875 		return 0;
876 	case AUTH_nosplit:
877 		outflags &= ~MD_An_split;
878 		outflags |= MD_An_nosplit;
879 		return 0;
880 	default:
881 		if (outflags & MD_An_split)
882 			outflags |= MD_br;
883 		else if (n->sec == SEC_AUTHORS &&
884 		    ! (outflags & MD_An_nosplit))
885 			outflags |= MD_An_split;
886 		return 1;
887 	}
888 }
889 
890 static int
891 md_pre_Ap(struct roff_node *n)
892 {
893 	outflags &= ~MD_spc;
894 	md_word("'");
895 	outflags &= ~MD_spc;
896 	return 0;
897 }
898 
899 static int
900 md_pre_Bd(struct roff_node *n)
901 {
902 	switch (n->norm->Bd.type) {
903 	case DISP_unfilled:
904 	case DISP_literal:
905 		return md_pre_Dl(n);
906 	default:
907 		return md_pre_D1(n);
908 	}
909 }
910 
911 static int
912 md_pre_Bk(struct roff_node *n)
913 {
914 	switch (n->type) {
915 	case ROFFT_BLOCK:
916 		return 1;
917 	case ROFFT_BODY:
918 		outflags |= MD_Bk;
919 		return 1;
920 	default:
921 		return 0;
922 	}
923 }
924 
925 static void
926 md_post_Bk(struct roff_node *n)
927 {
928 	if (n->type == ROFFT_BODY)
929 		outflags &= ~MD_Bk;
930 }
931 
932 static int
933 md_pre_Bl(struct roff_node *n)
934 {
935 	n->norm->Bl.count = 0;
936 	if (n->norm->Bl.type == LIST_column)
937 		md_pre_Dl(n);
938 	outflags |= MD_sp;
939 	return 1;
940 }
941 
942 static void
943 md_post_Bl(struct roff_node *n)
944 {
945 	n->norm->Bl.count = 0;
946 	if (n->norm->Bl.type == LIST_column)
947 		md_post_D1(n);
948 	outflags |= MD_sp;
949 }
950 
951 static int
952 md_pre_D1(struct roff_node *n)
953 {
954 	/*
955 	 * Markdown blockquote syntax does not work inside code blocks.
956 	 * The best we can do is fall back to another nested code block.
957 	 */
958 	if (code_blocks) {
959 		md_stack('\t');
960 		code_blocks++;
961 	} else {
962 		md_stack('>');
963 		quote_blocks++;
964 	}
965 	outflags |= MD_sp;
966 	return 1;
967 }
968 
969 static void
970 md_post_D1(struct roff_node *n)
971 {
972 	md_stack((char)-1);
973 	if (code_blocks)
974 		code_blocks--;
975 	else
976 		quote_blocks--;
977 	outflags |= MD_sp;
978 }
979 
980 static int
981 md_pre_Dl(struct roff_node *n)
982 {
983 	/*
984 	 * Markdown code block syntax does not work inside blockquotes.
985 	 * The best we can do is fall back to another nested blockquote.
986 	 */
987 	if (quote_blocks) {
988 		md_stack('>');
989 		quote_blocks++;
990 	} else {
991 		md_stack('\t');
992 		code_blocks++;
993 	}
994 	outflags |= MD_sp;
995 	return 1;
996 }
997 
998 static int
999 md_pre_En(struct roff_node *n)
1000 {
1001 	if (n->norm->Es == NULL ||
1002 	    n->norm->Es->child == NULL)
1003 		return 1;
1004 
1005 	md_word(n->norm->Es->child->string);
1006 	outflags &= ~MD_spc;
1007 	return 1;
1008 }
1009 
1010 static void
1011 md_post_En(struct roff_node *n)
1012 {
1013 	if (n->norm->Es == NULL ||
1014 	    n->norm->Es->child == NULL ||
1015 	    n->norm->Es->child->next == NULL)
1016 		return;
1017 
1018 	outflags &= ~MD_spc;
1019 	md_word(n->norm->Es->child->next->string);
1020 }
1021 
1022 static int
1023 md_pre_Eo(struct roff_node *n)
1024 {
1025 	if (n->end == ENDBODY_NOT &&
1026 	    n->parent->head->child == NULL &&
1027 	    n->child != NULL &&
1028 	    n->child->end != ENDBODY_NOT)
1029 		md_preword();
1030 	else if (n->end != ENDBODY_NOT ? n->child != NULL :
1031 	    n->parent->head->child != NULL && (n->child != NULL ||
1032 	    (n->parent->tail != NULL && n->parent->tail->child != NULL)))
1033 		outflags &= ~(MD_spc | MD_nl);
1034 	return 1;
1035 }
1036 
1037 static void
1038 md_post_Eo(struct roff_node *n)
1039 {
1040 	if (n->end != ENDBODY_NOT) {
1041 		outflags |= MD_spc;
1042 		return;
1043 	}
1044 
1045 	if (n->child == NULL && n->parent->head->child == NULL)
1046 		return;
1047 
1048 	if (n->parent->tail != NULL && n->parent->tail->child != NULL)
1049 		outflags &= ~MD_spc;
1050         else
1051 		outflags |= MD_spc;
1052 }
1053 
1054 static int
1055 md_pre_Fa(struct roff_node *n)
1056 {
1057 	int	 am_Fa;
1058 
1059 	am_Fa = n->tok == MDOC_Fa;
1060 
1061 	if (am_Fa)
1062 		n = n->child;
1063 
1064 	while (n != NULL) {
1065 		md_rawword("*");
1066 		outflags &= ~MD_spc;
1067 		md_node(n);
1068 		outflags &= ~MD_spc;
1069 		md_rawword("*");
1070 		if ((n = n->next) != NULL)
1071 			md_word(",");
1072 	}
1073 	return 0;
1074 }
1075 
1076 static void
1077 md_post_Fa(struct roff_node *n)
1078 {
1079 	struct roff_node *nn;
1080 
1081 	if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa)
1082 		md_word(",");
1083 }
1084 
1085 static int
1086 md_pre_Fd(struct roff_node *n)
1087 {
1088 	md_pre_syn(n);
1089 	md_pre_raw(n);
1090 	return 1;
1091 }
1092 
1093 static void
1094 md_post_Fd(struct roff_node *n)
1095 {
1096 	md_post_raw(n);
1097 	outflags |= MD_br;
1098 }
1099 
1100 static void
1101 md_post_Fl(struct roff_node *n)
1102 {
1103 	struct roff_node *nn;
1104 
1105 	md_post_raw(n);
1106 	if (n->child == NULL && (nn = roff_node_next(n)) != NULL &&
1107 	    nn->type != ROFFT_TEXT && (nn->flags & NODE_LINE) == 0)
1108 		outflags &= ~MD_spc;
1109 }
1110 
1111 static int
1112 md_pre_Fn(struct roff_node *n)
1113 {
1114 	md_pre_syn(n);
1115 
1116 	if ((n = n->child) == NULL)
1117 		return 0;
1118 
1119 	md_rawword("**");
1120 	outflags &= ~MD_spc;
1121 	md_node(n);
1122 	outflags &= ~MD_spc;
1123 	md_rawword("**");
1124 	outflags &= ~MD_spc;
1125 	md_word("(");
1126 
1127 	if ((n = n->next) != NULL)
1128 		md_pre_Fa(n);
1129 	return 0;
1130 }
1131 
1132 static void
1133 md_post_Fn(struct roff_node *n)
1134 {
1135 	md_word(")");
1136 	if (n->flags & NODE_SYNPRETTY) {
1137 		md_word(";");
1138 		outflags |= MD_sp;
1139 	}
1140 }
1141 
1142 static int
1143 md_pre_Fo(struct roff_node *n)
1144 {
1145 	switch (n->type) {
1146 	case ROFFT_BLOCK:
1147 		md_pre_syn(n);
1148 		break;
1149 	case ROFFT_HEAD:
1150 		if (n->child == NULL)
1151 			return 0;
1152 		md_pre_raw(n);
1153 		break;
1154 	case ROFFT_BODY:
1155 		outflags &= ~(MD_spc | MD_nl);
1156 		md_word("(");
1157 		break;
1158 	default:
1159 		break;
1160 	}
1161 	return 1;
1162 }
1163 
1164 static void
1165 md_post_Fo(struct roff_node *n)
1166 {
1167 	switch (n->type) {
1168 	case ROFFT_HEAD:
1169 		if (n->child != NULL)
1170 			md_post_raw(n);
1171 		break;
1172 	case ROFFT_BODY:
1173 		md_post_Fn(n);
1174 		break;
1175 	default:
1176 		break;
1177 	}
1178 }
1179 
1180 static int
1181 md_pre_In(struct roff_node *n)
1182 {
1183 	if (n->flags & NODE_SYNPRETTY) {
1184 		md_pre_syn(n);
1185 		md_rawword("**");
1186 		outflags &= ~MD_spc;
1187 		md_word("#include <");
1188 	} else {
1189 		md_word("<");
1190 		outflags &= ~MD_spc;
1191 		md_rawword("*");
1192 	}
1193 	outflags &= ~MD_spc;
1194 	return 1;
1195 }
1196 
1197 static void
1198 md_post_In(struct roff_node *n)
1199 {
1200 	if (n->flags & NODE_SYNPRETTY) {
1201 		outflags &= ~MD_spc;
1202 		md_rawword(">**");
1203 		outflags |= MD_nl;
1204 	} else {
1205 		outflags &= ~MD_spc;
1206 		md_rawword("*>");
1207 	}
1208 }
1209 
1210 static int
1211 md_pre_It(struct roff_node *n)
1212 {
1213 	struct roff_node	*bln;
1214 
1215 	switch (n->type) {
1216 	case ROFFT_BLOCK:
1217 		return 1;
1218 
1219 	case ROFFT_HEAD:
1220 		bln = n->parent->parent;
1221 		if (bln->norm->Bl.comp == 0 &&
1222 		    bln->norm->Bl.type != LIST_column)
1223 			outflags |= MD_sp;
1224 		outflags |= MD_nl;
1225 
1226 		switch (bln->norm->Bl.type) {
1227 		case LIST_item:
1228 			outflags |= MD_br;
1229 			return 0;
1230 		case LIST_inset:
1231 		case LIST_diag:
1232 		case LIST_ohang:
1233 			outflags |= MD_br;
1234 			return 1;
1235 		case LIST_tag:
1236 		case LIST_hang:
1237 			outflags |= MD_sp;
1238 			return 1;
1239 		case LIST_bullet:
1240 			md_rawword("*\t");
1241 			break;
1242 		case LIST_dash:
1243 		case LIST_hyphen:
1244 			md_rawword("-\t");
1245 			break;
1246 		case LIST_enum:
1247 			md_preword();
1248 			if (bln->norm->Bl.count < 99)
1249 				bln->norm->Bl.count++;
1250 			printf("%d.\t", bln->norm->Bl.count);
1251 			escflags &= ~ESC_FON;
1252 			break;
1253 		case LIST_column:
1254 			outflags |= MD_br;
1255 			return 0;
1256 		default:
1257 			return 0;
1258 		}
1259 		outflags &= ~MD_spc;
1260 		outflags |= MD_nonl;
1261 		outcount = 0;
1262 		md_stack('\t');
1263 		if (code_blocks || quote_blocks)
1264 			list_blocks++;
1265 		return 0;
1266 
1267 	case ROFFT_BODY:
1268 		bln = n->parent->parent;
1269 		switch (bln->norm->Bl.type) {
1270 		case LIST_ohang:
1271 			outflags |= MD_br;
1272 			break;
1273 		case LIST_tag:
1274 		case LIST_hang:
1275 			md_pre_D1(n);
1276 			break;
1277 		default:
1278 			break;
1279 		}
1280 		return 1;
1281 
1282 	default:
1283 		return 0;
1284 	}
1285 }
1286 
1287 static void
1288 md_post_It(struct roff_node *n)
1289 {
1290 	struct roff_node	*bln;
1291 	int			 i, nc;
1292 
1293 	if (n->type != ROFFT_BODY)
1294 		return;
1295 
1296 	bln = n->parent->parent;
1297 	switch (bln->norm->Bl.type) {
1298 	case LIST_bullet:
1299 	case LIST_dash:
1300 	case LIST_hyphen:
1301 	case LIST_enum:
1302 		md_stack((char)-1);
1303 		if (code_blocks || quote_blocks)
1304 			list_blocks--;
1305 		break;
1306 	case LIST_tag:
1307 	case LIST_hang:
1308 		md_post_D1(n);
1309 		break;
1310 
1311 	case LIST_column:
1312 		if (n->next == NULL)
1313 			break;
1314 
1315 		/* Calculate the array index of the current column. */
1316 
1317 		i = 0;
1318 		while ((n = n->prev) != NULL && n->type != ROFFT_HEAD)
1319 			i++;
1320 
1321 		/*
1322 		 * If a width was specified for this column,
1323 		 * subtract what printed, and
1324 		 * add the same spacing as in mdoc_term.c.
1325 		 */
1326 
1327 		nc = bln->norm->Bl.ncols;
1328 		i = i < nc ? strlen(bln->norm->Bl.cols[i]) - outcount +
1329 		    (nc < 5 ? 4 : nc == 5 ? 3 : 1) : 1;
1330 		if (i < 1)
1331 			i = 1;
1332 		while (i-- > 0)
1333 			putchar(' ');
1334 
1335 		outflags &= ~MD_spc;
1336 		escflags &= ~ESC_FON;
1337 		outcount = 0;
1338 		break;
1339 
1340 	default:
1341 		break;
1342 	}
1343 }
1344 
1345 static void
1346 md_post_Lb(struct roff_node *n)
1347 {
1348 	if (n->sec == SEC_LIBRARY)
1349 		outflags |= MD_br;
1350 }
1351 
1352 static void
1353 md_uri(const char *s)
1354 {
1355 	while (*s != '\0') {
1356 		if (strchr("%()<>", *s) != NULL) {
1357 			printf("%%%2.2hhX", *s);
1358 			outcount += 3;
1359 		} else {
1360 			putchar(*s);
1361 			outcount++;
1362 		}
1363 		s++;
1364 	}
1365 }
1366 
1367 static int
1368 md_pre_Lk(struct roff_node *n)
1369 {
1370 	const struct roff_node *link, *descr, *punct;
1371 
1372 	if ((link = n->child) == NULL)
1373 		return 0;
1374 
1375 	/* Find beginning of trailing punctuation. */
1376 	punct = n->last;
1377 	while (punct != link && punct->flags & NODE_DELIMC)
1378 		punct = punct->prev;
1379 	punct = punct->next;
1380 
1381 	/* Link text. */
1382 	descr = link->next;
1383 	if (descr == punct)
1384 		descr = link;  /* no text */
1385 	md_rawword("[");
1386 	outflags &= ~MD_spc;
1387 	do {
1388 		md_word(descr->string);
1389 		descr = descr->next;
1390 	} while (descr != punct);
1391 	outflags &= ~MD_spc;
1392 
1393 	/* Link target. */
1394 	md_rawword("](");
1395 	md_uri(link->string);
1396 	outflags &= ~MD_spc;
1397 	md_rawword(")");
1398 
1399 	/* Trailing punctuation. */
1400 	while (punct != NULL) {
1401 		md_word(punct->string);
1402 		punct = punct->next;
1403 	}
1404 	return 0;
1405 }
1406 
1407 static int
1408 md_pre_Mt(struct roff_node *n)
1409 {
1410 	const struct roff_node *nch;
1411 
1412 	md_rawword("[");
1413 	outflags &= ~MD_spc;
1414 	for (nch = n->child; nch != NULL; nch = nch->next)
1415 		md_word(nch->string);
1416 	outflags &= ~MD_spc;
1417 	md_rawword("](mailto:");
1418 	for (nch = n->child; nch != NULL; nch = nch->next) {
1419 		md_uri(nch->string);
1420 		if (nch->next != NULL) {
1421 			putchar(' ');
1422 			outcount++;
1423 		}
1424 	}
1425 	outflags &= ~MD_spc;
1426 	md_rawword(")");
1427 	return 0;
1428 }
1429 
1430 static int
1431 md_pre_Nd(struct roff_node *n)
1432 {
1433 	outflags &= ~MD_nl;
1434 	outflags |= MD_spc;
1435 	md_word("-");
1436 	return 1;
1437 }
1438 
1439 static int
1440 md_pre_Nm(struct roff_node *n)
1441 {
1442 	switch (n->type) {
1443 	case ROFFT_BLOCK:
1444 		outflags |= MD_Bk;
1445 		md_pre_syn(n);
1446 		break;
1447 	case ROFFT_HEAD:
1448 	case ROFFT_ELEM:
1449 		md_pre_raw(n);
1450 		break;
1451 	default:
1452 		break;
1453 	}
1454 	return 1;
1455 }
1456 
1457 static void
1458 md_post_Nm(struct roff_node *n)
1459 {
1460 	switch (n->type) {
1461 	case ROFFT_BLOCK:
1462 		outflags &= ~MD_Bk;
1463 		break;
1464 	case ROFFT_HEAD:
1465 	case ROFFT_ELEM:
1466 		md_post_raw(n);
1467 		break;
1468 	default:
1469 		break;
1470 	}
1471 }
1472 
1473 static int
1474 md_pre_No(struct roff_node *n)
1475 {
1476 	outflags |= MD_spc_force;
1477 	return 1;
1478 }
1479 
1480 static int
1481 md_pre_Ns(struct roff_node *n)
1482 {
1483 	outflags &= ~MD_spc;
1484 	return 0;
1485 }
1486 
1487 static void
1488 md_post_Pf(struct roff_node *n)
1489 {
1490 	if (n->next != NULL && (n->next->flags & NODE_LINE) == 0)
1491 		outflags &= ~MD_spc;
1492 }
1493 
1494 static int
1495 md_pre_Pp(struct roff_node *n)
1496 {
1497 	outflags |= MD_sp;
1498 	return 0;
1499 }
1500 
1501 static int
1502 md_pre_Rs(struct roff_node *n)
1503 {
1504 	if (n->sec == SEC_SEE_ALSO)
1505 		outflags |= MD_sp;
1506 	return 1;
1507 }
1508 
1509 static int
1510 md_pre_Sh(struct roff_node *n)
1511 {
1512 	switch (n->type) {
1513 	case ROFFT_BLOCK:
1514 		if (n->sec == SEC_AUTHORS)
1515 			outflags &= ~(MD_An_split | MD_An_nosplit);
1516 		break;
1517 	case ROFFT_HEAD:
1518 		outflags |= MD_sp;
1519 		md_rawword(n->tok == MDOC_Sh ? "#" : "##");
1520 		break;
1521 	case ROFFT_BODY:
1522 		outflags |= MD_sp;
1523 		break;
1524 	default:
1525 		break;
1526 	}
1527 	return 1;
1528 }
1529 
1530 static int
1531 md_pre_Sm(struct roff_node *n)
1532 {
1533 	if (n->child == NULL)
1534 		outflags ^= MD_Sm;
1535 	else if (strcmp("on", n->child->string) == 0)
1536 		outflags |= MD_Sm;
1537 	else
1538 		outflags &= ~MD_Sm;
1539 
1540 	if (outflags & MD_Sm)
1541 		outflags |= MD_spc;
1542 
1543 	return 0;
1544 }
1545 
1546 static int
1547 md_pre_Vt(struct roff_node *n)
1548 {
1549 	switch (n->type) {
1550 	case ROFFT_BLOCK:
1551 		md_pre_syn(n);
1552 		return 1;
1553 	case ROFFT_BODY:
1554 	case ROFFT_ELEM:
1555 		md_pre_raw(n);
1556 		return 1;
1557 	default:
1558 		return 0;
1559 	}
1560 }
1561 
1562 static void
1563 md_post_Vt(struct roff_node *n)
1564 {
1565 	switch (n->type) {
1566 	case ROFFT_BODY:
1567 	case ROFFT_ELEM:
1568 		md_post_raw(n);
1569 		break;
1570 	default:
1571 		break;
1572 	}
1573 }
1574 
1575 static int
1576 md_pre_Xr(struct roff_node *n)
1577 {
1578 	n = n->child;
1579 	if (n == NULL)
1580 		return 0;
1581 	md_node(n);
1582 	n = n->next;
1583 	if (n == NULL)
1584 		return 0;
1585 	outflags &= ~MD_spc;
1586 	md_word("(");
1587 	md_node(n);
1588 	md_word(")");
1589 	return 0;
1590 }
1591 
1592 static int
1593 md_pre__R(struct roff_node *n)
1594 {
1595 	const unsigned char	*cp;
1596 	const char		*arg;
1597 
1598 	arg = n->child->string;
1599 
1600 	if (strncmp(arg, "RFC ", 4) != 0)
1601 		return 1;
1602 	cp = arg += 4;
1603 	while (isdigit(*cp))
1604 		cp++;
1605 	if (*cp != '\0')
1606 		return 1;
1607 
1608 	md_rawword("[RFC ");
1609 	outflags &= ~MD_spc;
1610 	md_rawword(arg);
1611 	outflags &= ~MD_spc;
1612 	md_rawword("](http://www.rfc-editor.org/rfc/rfc");
1613 	outflags &= ~MD_spc;
1614 	md_rawword(arg);
1615 	outflags &= ~MD_spc;
1616 	md_rawword(".html)");
1617 	return 0;
1618 }
1619 
1620 static int
1621 md_pre__T(struct roff_node *n)
1622 {
1623 	if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
1624 		md_word("\"");
1625 	else
1626 		md_rawword("*");
1627 	outflags &= ~MD_spc;
1628 	return 1;
1629 }
1630 
1631 static void
1632 md_post__T(struct roff_node *n)
1633 {
1634 	outflags &= ~MD_spc;
1635 	if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
1636 		md_word("\"");
1637 	else
1638 		md_rawword("*");
1639 	md_post_pc(n);
1640 }
1641 
1642 static int
1643 md_pre_br(struct roff_node *n)
1644 {
1645 	outflags |= MD_br;
1646 	return 0;
1647 }
1648