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