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