xref: /openbsd/usr.bin/mandoc/mdoc_markdown.c (revision 5dea098c)
1 /* $OpenBSD: mdoc_markdown.c,v 1.36 2021/08/10 12:36:42 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 			case ESCAPE_FONTCB:
605 				nextfont = "**";
606 				break;
607 			case ESCAPE_FONTITALIC:
608 			case ESCAPE_FONTCI:
609 				nextfont = "*";
610 				break;
611 			case ESCAPE_FONTBI:
612 				nextfont = "***";
613 				break;
614 			case ESCAPE_FONT:
615 			case ESCAPE_FONTCR:
616 			case ESCAPE_FONTROMAN:
617 				nextfont = "";
618 				break;
619 			case ESCAPE_FONTPREV:
620 				nextfont = prevfont;
621 				break;
622 			case ESCAPE_BREAK:
623 				breakline = 1;
624 				break;
625 			case ESCAPE_NOSPACE:
626 			case ESCAPE_SKIPCHAR:
627 			case ESCAPE_OVERSTRIKE:
628 				/* XXX not implemented */
629 				/* FALLTHROUGH */
630 			case ESCAPE_ERROR:
631 			default:
632 				break;
633 			}
634 			if (nextfont != NULL && !code_blocks) {
635 				if (*currfont != '\0') {
636 					outflags &= ~MD_spc;
637 					md_rawword(currfont);
638 				}
639 				prevfont = currfont;
640 				currfont = nextfont;
641 				if (*currfont != '\0') {
642 					outflags &= ~MD_spc;
643 					md_rawword(currfont);
644 				}
645 			}
646 			if (uc) {
647 				if ((uc < 0x20 && uc != 0x09) ||
648 				    (uc > 0x7E && uc < 0xA0))
649 					uc = 0xFFFD;
650 				if (code_blocks) {
651 					seq = mchars_uc2str(uc);
652 					fputs(seq, stdout);
653 					outcount += strlen(seq);
654 				} else {
655 					printf("&#%d;", uc);
656 					outcount++;
657 				}
658 				escflags &= ~ESC_FON;
659 			}
660 			c = '\0';
661 			break;
662 		case ']':
663 			bs = escflags & ESC_SQU && !code_blocks;
664 			escflags |= ESC_HYP;
665 			break;
666 		default:
667 			break;
668 		}
669 		if (bs)
670 			putchar('\\');
671 		md_char(c);
672 		if (breakline &&
673 		    (*s == '\0' || *s == ' ' || *s == ASCII_NBRSP)) {
674 			printf("  \n");
675 			breakline = 0;
676 			while (*s == ' ' || *s == ASCII_NBRSP)
677 				s++;
678 		}
679 	}
680 	if (*currfont != '\0') {
681 		outflags &= ~MD_spc;
682 		md_rawword(currfont);
683 	} else if (s[-2] == ' ')
684 		escflags |= ESC_EOL;
685 	else
686 		escflags &= ~ESC_EOL;
687 }
688 
689 /*
690  * Print a single HTML named character reference.
691  */
692 static void
693 md_named(const char *s)
694 {
695 	printf("&%s;", s);
696 	escflags &= ~(ESC_FON | ESC_EOL);
697 	outcount++;
698 }
699 
700 /*
701  * Print a single raw character and maintain certain escape flags.
702  */
703 static void
704 md_char(unsigned char c)
705 {
706 	if (c != '\0') {
707 		putchar(c);
708 		if (c == '*')
709 			escflags |= ESC_FON;
710 		else
711 			escflags &= ~ESC_FON;
712 		outcount++;
713 	}
714 	if (c != ']')
715 		escflags &= ~ESC_HYP;
716 	if (c == ' ' || c == '\t' || c == '>')
717 		return;
718 	if (isdigit(c) == 0)
719 		escflags &= ~ESC_NUM;
720 	else if (escflags & ESC_BOL)
721 		escflags |= ESC_NUM;
722 	escflags &= ~ESC_BOL;
723 }
724 
725 static int
726 md_cond_head(struct roff_node *n)
727 {
728 	return n->type == ROFFT_HEAD;
729 }
730 
731 static int
732 md_cond_body(struct roff_node *n)
733 {
734 	return n->type == ROFFT_BODY;
735 }
736 
737 static int
738 md_pre_abort(struct roff_node *n)
739 {
740 	abort();
741 }
742 
743 static int
744 md_pre_raw(struct roff_node *n)
745 {
746 	const char	*prefix;
747 
748 	if ((prefix = md_act(n->tok)->prefix) != NULL) {
749 		md_rawword(prefix);
750 		outflags &= ~MD_spc;
751 		if (*prefix == '`')
752 			code_blocks++;
753 	}
754 	return 1;
755 }
756 
757 static void
758 md_post_raw(struct roff_node *n)
759 {
760 	const char	*suffix;
761 
762 	if ((suffix = md_act(n->tok)->suffix) != NULL) {
763 		outflags &= ~(MD_spc | MD_nl);
764 		md_rawword(suffix);
765 		if (*suffix == '`')
766 			code_blocks--;
767 	}
768 }
769 
770 static int
771 md_pre_word(struct roff_node *n)
772 {
773 	const char	*prefix;
774 
775 	if ((prefix = md_act(n->tok)->prefix) != NULL) {
776 		md_word(prefix);
777 		outflags &= ~MD_spc;
778 	}
779 	return 1;
780 }
781 
782 static void
783 md_post_word(struct roff_node *n)
784 {
785 	const char	*suffix;
786 
787 	if ((suffix = md_act(n->tok)->suffix) != NULL) {
788 		outflags &= ~(MD_spc | MD_nl);
789 		md_word(suffix);
790 	}
791 }
792 
793 static void
794 md_post_pc(struct roff_node *n)
795 {
796 	struct roff_node *nn;
797 
798 	md_post_raw(n);
799 	if (n->parent->tok != MDOC_Rs)
800 		return;
801 
802 	if ((nn = roff_node_next(n)) != NULL) {
803 		md_word(",");
804 		if (nn->tok == n->tok &&
805 		    (nn = roff_node_prev(n)) != NULL &&
806 		    nn->tok == n->tok)
807 			md_word("and");
808 	} else {
809 		md_word(".");
810 		outflags |= MD_nl;
811 	}
812 }
813 
814 static int
815 md_pre_skip(struct roff_node *n)
816 {
817 	return 0;
818 }
819 
820 static void
821 md_pre_syn(struct roff_node *n)
822 {
823 	struct roff_node *np;
824 
825 	if ((n->flags & NODE_SYNPRETTY) == 0 ||
826 	    (np = roff_node_prev(n)) == NULL)
827 		return;
828 
829 	if (np->tok == n->tok &&
830 	    n->tok != MDOC_Ft &&
831 	    n->tok != MDOC_Fo &&
832 	    n->tok != MDOC_Fn) {
833 		outflags |= MD_br;
834 		return;
835 	}
836 
837 	switch (np->tok) {
838 	case MDOC_Fd:
839 	case MDOC_Fn:
840 	case MDOC_Fo:
841 	case MDOC_In:
842 	case MDOC_Vt:
843 		outflags |= MD_sp;
844 		break;
845 	case MDOC_Ft:
846 		if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) {
847 			outflags |= MD_sp;
848 			break;
849 		}
850 		/* FALLTHROUGH */
851 	default:
852 		outflags |= MD_br;
853 		break;
854 	}
855 }
856 
857 static int
858 md_pre_An(struct roff_node *n)
859 {
860 	switch (n->norm->An.auth) {
861 	case AUTH_split:
862 		outflags &= ~MD_An_nosplit;
863 		outflags |= MD_An_split;
864 		return 0;
865 	case AUTH_nosplit:
866 		outflags &= ~MD_An_split;
867 		outflags |= MD_An_nosplit;
868 		return 0;
869 	default:
870 		if (outflags & MD_An_split)
871 			outflags |= MD_br;
872 		else if (n->sec == SEC_AUTHORS &&
873 		    ! (outflags & MD_An_nosplit))
874 			outflags |= MD_An_split;
875 		return 1;
876 	}
877 }
878 
879 static int
880 md_pre_Ap(struct roff_node *n)
881 {
882 	outflags &= ~MD_spc;
883 	md_word("'");
884 	outflags &= ~MD_spc;
885 	return 0;
886 }
887 
888 static int
889 md_pre_Bd(struct roff_node *n)
890 {
891 	switch (n->norm->Bd.type) {
892 	case DISP_unfilled:
893 	case DISP_literal:
894 		return md_pre_Dl(n);
895 	default:
896 		return md_pre_D1(n);
897 	}
898 }
899 
900 static int
901 md_pre_Bk(struct roff_node *n)
902 {
903 	switch (n->type) {
904 	case ROFFT_BLOCK:
905 		return 1;
906 	case ROFFT_BODY:
907 		outflags |= MD_Bk;
908 		return 1;
909 	default:
910 		return 0;
911 	}
912 }
913 
914 static void
915 md_post_Bk(struct roff_node *n)
916 {
917 	if (n->type == ROFFT_BODY)
918 		outflags &= ~MD_Bk;
919 }
920 
921 static int
922 md_pre_Bl(struct roff_node *n)
923 {
924 	n->norm->Bl.count = 0;
925 	if (n->norm->Bl.type == LIST_column)
926 		md_pre_Dl(n);
927 	outflags |= MD_sp;
928 	return 1;
929 }
930 
931 static void
932 md_post_Bl(struct roff_node *n)
933 {
934 	n->norm->Bl.count = 0;
935 	if (n->norm->Bl.type == LIST_column)
936 		md_post_D1(n);
937 	outflags |= MD_sp;
938 }
939 
940 static int
941 md_pre_D1(struct roff_node *n)
942 {
943 	/*
944 	 * Markdown blockquote syntax does not work inside code blocks.
945 	 * The best we can do is fall back to another nested code block.
946 	 */
947 	if (code_blocks) {
948 		md_stack('\t');
949 		code_blocks++;
950 	} else {
951 		md_stack('>');
952 		quote_blocks++;
953 	}
954 	outflags |= MD_sp;
955 	return 1;
956 }
957 
958 static void
959 md_post_D1(struct roff_node *n)
960 {
961 	md_stack((char)-1);
962 	if (code_blocks)
963 		code_blocks--;
964 	else
965 		quote_blocks--;
966 	outflags |= MD_sp;
967 }
968 
969 static int
970 md_pre_Dl(struct roff_node *n)
971 {
972 	/*
973 	 * Markdown code block syntax does not work inside blockquotes.
974 	 * The best we can do is fall back to another nested blockquote.
975 	 */
976 	if (quote_blocks) {
977 		md_stack('>');
978 		quote_blocks++;
979 	} else {
980 		md_stack('\t');
981 		code_blocks++;
982 	}
983 	outflags |= MD_sp;
984 	return 1;
985 }
986 
987 static int
988 md_pre_En(struct roff_node *n)
989 {
990 	if (n->norm->Es == NULL ||
991 	    n->norm->Es->child == NULL)
992 		return 1;
993 
994 	md_word(n->norm->Es->child->string);
995 	outflags &= ~MD_spc;
996 	return 1;
997 }
998 
999 static void
1000 md_post_En(struct roff_node *n)
1001 {
1002 	if (n->norm->Es == NULL ||
1003 	    n->norm->Es->child == NULL ||
1004 	    n->norm->Es->child->next == NULL)
1005 		return;
1006 
1007 	outflags &= ~MD_spc;
1008 	md_word(n->norm->Es->child->next->string);
1009 }
1010 
1011 static int
1012 md_pre_Eo(struct roff_node *n)
1013 {
1014 	if (n->end == ENDBODY_NOT &&
1015 	    n->parent->head->child == NULL &&
1016 	    n->child != NULL &&
1017 	    n->child->end != ENDBODY_NOT)
1018 		md_preword();
1019 	else if (n->end != ENDBODY_NOT ? n->child != NULL :
1020 	    n->parent->head->child != NULL && (n->child != NULL ||
1021 	    (n->parent->tail != NULL && n->parent->tail->child != NULL)))
1022 		outflags &= ~(MD_spc | MD_nl);
1023 	return 1;
1024 }
1025 
1026 static void
1027 md_post_Eo(struct roff_node *n)
1028 {
1029 	if (n->end != ENDBODY_NOT) {
1030 		outflags |= MD_spc;
1031 		return;
1032 	}
1033 
1034 	if (n->child == NULL && n->parent->head->child == NULL)
1035 		return;
1036 
1037 	if (n->parent->tail != NULL && n->parent->tail->child != NULL)
1038 		outflags &= ~MD_spc;
1039         else
1040 		outflags |= MD_spc;
1041 }
1042 
1043 static int
1044 md_pre_Fa(struct roff_node *n)
1045 {
1046 	int	 am_Fa;
1047 
1048 	am_Fa = n->tok == MDOC_Fa;
1049 
1050 	if (am_Fa)
1051 		n = n->child;
1052 
1053 	while (n != NULL) {
1054 		md_rawword("*");
1055 		outflags &= ~MD_spc;
1056 		md_node(n);
1057 		outflags &= ~MD_spc;
1058 		md_rawword("*");
1059 		if ((n = n->next) != NULL)
1060 			md_word(",");
1061 	}
1062 	return 0;
1063 }
1064 
1065 static void
1066 md_post_Fa(struct roff_node *n)
1067 {
1068 	struct roff_node *nn;
1069 
1070 	if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa)
1071 		md_word(",");
1072 }
1073 
1074 static int
1075 md_pre_Fd(struct roff_node *n)
1076 {
1077 	md_pre_syn(n);
1078 	md_pre_raw(n);
1079 	return 1;
1080 }
1081 
1082 static void
1083 md_post_Fd(struct roff_node *n)
1084 {
1085 	md_post_raw(n);
1086 	outflags |= MD_br;
1087 }
1088 
1089 static void
1090 md_post_Fl(struct roff_node *n)
1091 {
1092 	struct roff_node *nn;
1093 
1094 	md_post_raw(n);
1095 	if (n->child == NULL && (nn = roff_node_next(n)) != NULL &&
1096 	    nn->type != ROFFT_TEXT && (nn->flags & NODE_LINE) == 0)
1097 		outflags &= ~MD_spc;
1098 }
1099 
1100 static int
1101 md_pre_Fn(struct roff_node *n)
1102 {
1103 	md_pre_syn(n);
1104 
1105 	if ((n = n->child) == NULL)
1106 		return 0;
1107 
1108 	md_rawword("**");
1109 	outflags &= ~MD_spc;
1110 	md_node(n);
1111 	outflags &= ~MD_spc;
1112 	md_rawword("**");
1113 	outflags &= ~MD_spc;
1114 	md_word("(");
1115 
1116 	if ((n = n->next) != NULL)
1117 		md_pre_Fa(n);
1118 	return 0;
1119 }
1120 
1121 static void
1122 md_post_Fn(struct roff_node *n)
1123 {
1124 	md_word(")");
1125 	if (n->flags & NODE_SYNPRETTY) {
1126 		md_word(";");
1127 		outflags |= MD_sp;
1128 	}
1129 }
1130 
1131 static int
1132 md_pre_Fo(struct roff_node *n)
1133 {
1134 	switch (n->type) {
1135 	case ROFFT_BLOCK:
1136 		md_pre_syn(n);
1137 		break;
1138 	case ROFFT_HEAD:
1139 		if (n->child == NULL)
1140 			return 0;
1141 		md_pre_raw(n);
1142 		break;
1143 	case ROFFT_BODY:
1144 		outflags &= ~(MD_spc | MD_nl);
1145 		md_word("(");
1146 		break;
1147 	default:
1148 		break;
1149 	}
1150 	return 1;
1151 }
1152 
1153 static void
1154 md_post_Fo(struct roff_node *n)
1155 {
1156 	switch (n->type) {
1157 	case ROFFT_HEAD:
1158 		if (n->child != NULL)
1159 			md_post_raw(n);
1160 		break;
1161 	case ROFFT_BODY:
1162 		md_post_Fn(n);
1163 		break;
1164 	default:
1165 		break;
1166 	}
1167 }
1168 
1169 static int
1170 md_pre_In(struct roff_node *n)
1171 {
1172 	if (n->flags & NODE_SYNPRETTY) {
1173 		md_pre_syn(n);
1174 		md_rawword("**");
1175 		outflags &= ~MD_spc;
1176 		md_word("#include <");
1177 	} else {
1178 		md_word("<");
1179 		outflags &= ~MD_spc;
1180 		md_rawword("*");
1181 	}
1182 	outflags &= ~MD_spc;
1183 	return 1;
1184 }
1185 
1186 static void
1187 md_post_In(struct roff_node *n)
1188 {
1189 	if (n->flags & NODE_SYNPRETTY) {
1190 		outflags &= ~MD_spc;
1191 		md_rawword(">**");
1192 		outflags |= MD_nl;
1193 	} else {
1194 		outflags &= ~MD_spc;
1195 		md_rawword("*>");
1196 	}
1197 }
1198 
1199 static int
1200 md_pre_It(struct roff_node *n)
1201 {
1202 	struct roff_node	*bln;
1203 
1204 	switch (n->type) {
1205 	case ROFFT_BLOCK:
1206 		return 1;
1207 
1208 	case ROFFT_HEAD:
1209 		bln = n->parent->parent;
1210 		if (bln->norm->Bl.comp == 0 &&
1211 		    bln->norm->Bl.type != LIST_column)
1212 			outflags |= MD_sp;
1213 		outflags |= MD_nl;
1214 
1215 		switch (bln->norm->Bl.type) {
1216 		case LIST_item:
1217 			outflags |= MD_br;
1218 			return 0;
1219 		case LIST_inset:
1220 		case LIST_diag:
1221 		case LIST_ohang:
1222 			outflags |= MD_br;
1223 			return 1;
1224 		case LIST_tag:
1225 		case LIST_hang:
1226 			outflags |= MD_sp;
1227 			return 1;
1228 		case LIST_bullet:
1229 			md_rawword("*\t");
1230 			break;
1231 		case LIST_dash:
1232 		case LIST_hyphen:
1233 			md_rawword("-\t");
1234 			break;
1235 		case LIST_enum:
1236 			md_preword();
1237 			if (bln->norm->Bl.count < 99)
1238 				bln->norm->Bl.count++;
1239 			printf("%d.\t", bln->norm->Bl.count);
1240 			escflags &= ~ESC_FON;
1241 			break;
1242 		case LIST_column:
1243 			outflags |= MD_br;
1244 			return 0;
1245 		default:
1246 			return 0;
1247 		}
1248 		outflags &= ~MD_spc;
1249 		outflags |= MD_nonl;
1250 		outcount = 0;
1251 		md_stack('\t');
1252 		if (code_blocks || quote_blocks)
1253 			list_blocks++;
1254 		return 0;
1255 
1256 	case ROFFT_BODY:
1257 		bln = n->parent->parent;
1258 		switch (bln->norm->Bl.type) {
1259 		case LIST_ohang:
1260 			outflags |= MD_br;
1261 			break;
1262 		case LIST_tag:
1263 		case LIST_hang:
1264 			md_pre_D1(n);
1265 			break;
1266 		default:
1267 			break;
1268 		}
1269 		return 1;
1270 
1271 	default:
1272 		return 0;
1273 	}
1274 }
1275 
1276 static void
1277 md_post_It(struct roff_node *n)
1278 {
1279 	struct roff_node	*bln;
1280 	int			 i, nc;
1281 
1282 	if (n->type != ROFFT_BODY)
1283 		return;
1284 
1285 	bln = n->parent->parent;
1286 	switch (bln->norm->Bl.type) {
1287 	case LIST_bullet:
1288 	case LIST_dash:
1289 	case LIST_hyphen:
1290 	case LIST_enum:
1291 		md_stack((char)-1);
1292 		if (code_blocks || quote_blocks)
1293 			list_blocks--;
1294 		break;
1295 	case LIST_tag:
1296 	case LIST_hang:
1297 		md_post_D1(n);
1298 		break;
1299 
1300 	case LIST_column:
1301 		if (n->next == NULL)
1302 			break;
1303 
1304 		/* Calculate the array index of the current column. */
1305 
1306 		i = 0;
1307 		while ((n = n->prev) != NULL && n->type != ROFFT_HEAD)
1308 			i++;
1309 
1310 		/*
1311 		 * If a width was specified for this column,
1312 		 * subtract what printed, and
1313 		 * add the same spacing as in mdoc_term.c.
1314 		 */
1315 
1316 		nc = bln->norm->Bl.ncols;
1317 		i = i < nc ? strlen(bln->norm->Bl.cols[i]) - outcount +
1318 		    (nc < 5 ? 4 : nc == 5 ? 3 : 1) : 1;
1319 		if (i < 1)
1320 			i = 1;
1321 		while (i-- > 0)
1322 			putchar(' ');
1323 
1324 		outflags &= ~MD_spc;
1325 		escflags &= ~ESC_FON;
1326 		outcount = 0;
1327 		break;
1328 
1329 	default:
1330 		break;
1331 	}
1332 }
1333 
1334 static void
1335 md_post_Lb(struct roff_node *n)
1336 {
1337 	if (n->sec == SEC_LIBRARY)
1338 		outflags |= MD_br;
1339 }
1340 
1341 static void
1342 md_uri(const char *s)
1343 {
1344 	while (*s != '\0') {
1345 		if (strchr("%()<>", *s) != NULL) {
1346 			printf("%%%2.2hhX", *s);
1347 			outcount += 3;
1348 		} else {
1349 			putchar(*s);
1350 			outcount++;
1351 		}
1352 		s++;
1353 	}
1354 }
1355 
1356 static int
1357 md_pre_Lk(struct roff_node *n)
1358 {
1359 	const struct roff_node *link, *descr, *punct;
1360 
1361 	if ((link = n->child) == NULL)
1362 		return 0;
1363 
1364 	/* Find beginning of trailing punctuation. */
1365 	punct = n->last;
1366 	while (punct != link && punct->flags & NODE_DELIMC)
1367 		punct = punct->prev;
1368 	punct = punct->next;
1369 
1370 	/* Link text. */
1371 	descr = link->next;
1372 	if (descr == punct)
1373 		descr = link;  /* no text */
1374 	md_rawword("[");
1375 	outflags &= ~MD_spc;
1376 	do {
1377 		md_word(descr->string);
1378 		descr = descr->next;
1379 	} while (descr != punct);
1380 	outflags &= ~MD_spc;
1381 
1382 	/* Link target. */
1383 	md_rawword("](");
1384 	md_uri(link->string);
1385 	outflags &= ~MD_spc;
1386 	md_rawword(")");
1387 
1388 	/* Trailing punctuation. */
1389 	while (punct != NULL) {
1390 		md_word(punct->string);
1391 		punct = punct->next;
1392 	}
1393 	return 0;
1394 }
1395 
1396 static int
1397 md_pre_Mt(struct roff_node *n)
1398 {
1399 	const struct roff_node *nch;
1400 
1401 	md_rawword("[");
1402 	outflags &= ~MD_spc;
1403 	for (nch = n->child; nch != NULL; nch = nch->next)
1404 		md_word(nch->string);
1405 	outflags &= ~MD_spc;
1406 	md_rawword("](mailto:");
1407 	for (nch = n->child; nch != NULL; nch = nch->next) {
1408 		md_uri(nch->string);
1409 		if (nch->next != NULL) {
1410 			putchar(' ');
1411 			outcount++;
1412 		}
1413 	}
1414 	outflags &= ~MD_spc;
1415 	md_rawword(")");
1416 	return 0;
1417 }
1418 
1419 static int
1420 md_pre_Nd(struct roff_node *n)
1421 {
1422 	outflags &= ~MD_nl;
1423 	outflags |= MD_spc;
1424 	md_word("-");
1425 	return 1;
1426 }
1427 
1428 static int
1429 md_pre_Nm(struct roff_node *n)
1430 {
1431 	switch (n->type) {
1432 	case ROFFT_BLOCK:
1433 		outflags |= MD_Bk;
1434 		md_pre_syn(n);
1435 		break;
1436 	case ROFFT_HEAD:
1437 	case ROFFT_ELEM:
1438 		md_pre_raw(n);
1439 		break;
1440 	default:
1441 		break;
1442 	}
1443 	return 1;
1444 }
1445 
1446 static void
1447 md_post_Nm(struct roff_node *n)
1448 {
1449 	switch (n->type) {
1450 	case ROFFT_BLOCK:
1451 		outflags &= ~MD_Bk;
1452 		break;
1453 	case ROFFT_HEAD:
1454 	case ROFFT_ELEM:
1455 		md_post_raw(n);
1456 		break;
1457 	default:
1458 		break;
1459 	}
1460 }
1461 
1462 static int
1463 md_pre_No(struct roff_node *n)
1464 {
1465 	outflags |= MD_spc_force;
1466 	return 1;
1467 }
1468 
1469 static int
1470 md_pre_Ns(struct roff_node *n)
1471 {
1472 	outflags &= ~MD_spc;
1473 	return 0;
1474 }
1475 
1476 static void
1477 md_post_Pf(struct roff_node *n)
1478 {
1479 	if (n->next != NULL && (n->next->flags & NODE_LINE) == 0)
1480 		outflags &= ~MD_spc;
1481 }
1482 
1483 static int
1484 md_pre_Pp(struct roff_node *n)
1485 {
1486 	outflags |= MD_sp;
1487 	return 0;
1488 }
1489 
1490 static int
1491 md_pre_Rs(struct roff_node *n)
1492 {
1493 	if (n->sec == SEC_SEE_ALSO)
1494 		outflags |= MD_sp;
1495 	return 1;
1496 }
1497 
1498 static int
1499 md_pre_Sh(struct roff_node *n)
1500 {
1501 	switch (n->type) {
1502 	case ROFFT_BLOCK:
1503 		if (n->sec == SEC_AUTHORS)
1504 			outflags &= ~(MD_An_split | MD_An_nosplit);
1505 		break;
1506 	case ROFFT_HEAD:
1507 		outflags |= MD_sp;
1508 		md_rawword(n->tok == MDOC_Sh ? "#" : "##");
1509 		break;
1510 	case ROFFT_BODY:
1511 		outflags |= MD_sp;
1512 		break;
1513 	default:
1514 		break;
1515 	}
1516 	return 1;
1517 }
1518 
1519 static int
1520 md_pre_Sm(struct roff_node *n)
1521 {
1522 	if (n->child == NULL)
1523 		outflags ^= MD_Sm;
1524 	else if (strcmp("on", n->child->string) == 0)
1525 		outflags |= MD_Sm;
1526 	else
1527 		outflags &= ~MD_Sm;
1528 
1529 	if (outflags & MD_Sm)
1530 		outflags |= MD_spc;
1531 
1532 	return 0;
1533 }
1534 
1535 static int
1536 md_pre_Vt(struct roff_node *n)
1537 {
1538 	switch (n->type) {
1539 	case ROFFT_BLOCK:
1540 		md_pre_syn(n);
1541 		return 1;
1542 	case ROFFT_BODY:
1543 	case ROFFT_ELEM:
1544 		md_pre_raw(n);
1545 		return 1;
1546 	default:
1547 		return 0;
1548 	}
1549 }
1550 
1551 static void
1552 md_post_Vt(struct roff_node *n)
1553 {
1554 	switch (n->type) {
1555 	case ROFFT_BODY:
1556 	case ROFFT_ELEM:
1557 		md_post_raw(n);
1558 		break;
1559 	default:
1560 		break;
1561 	}
1562 }
1563 
1564 static int
1565 md_pre_Xr(struct roff_node *n)
1566 {
1567 	n = n->child;
1568 	if (n == NULL)
1569 		return 0;
1570 	md_node(n);
1571 	n = n->next;
1572 	if (n == NULL)
1573 		return 0;
1574 	outflags &= ~MD_spc;
1575 	md_word("(");
1576 	md_node(n);
1577 	md_word(")");
1578 	return 0;
1579 }
1580 
1581 static int
1582 md_pre__T(struct roff_node *n)
1583 {
1584 	if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
1585 		md_word("\"");
1586 	else
1587 		md_rawword("*");
1588 	outflags &= ~MD_spc;
1589 	return 1;
1590 }
1591 
1592 static void
1593 md_post__T(struct roff_node *n)
1594 {
1595 	outflags &= ~MD_spc;
1596 	if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
1597 		md_word("\"");
1598 	else
1599 		md_rawword("*");
1600 	md_post_pc(n);
1601 }
1602 
1603 static int
1604 md_pre_br(struct roff_node *n)
1605 {
1606 	outflags |= MD_br;
1607 	return 0;
1608 }
1609