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