1*fc3ee6fdSchristos /*	Id: mdoc_markdown.c,v 1.30 2018/12/30 00:49:55 schwarze Exp  */
29d9b81f7Schristos /*
3*fc3ee6fdSchristos  * Copyright (c) 2017, 2018 Ingo Schwarze <schwarze@openbsd.org>
49d9b81f7Schristos  *
59d9b81f7Schristos  * Permission to use, copy, modify, and distribute this software for any
69d9b81f7Schristos  * purpose with or without fee is hereby granted, provided that the above
79d9b81f7Schristos  * copyright notice and this permission notice appear in all copies.
89d9b81f7Schristos  *
99d9b81f7Schristos  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
109d9b81f7Schristos  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
119d9b81f7Schristos  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
129d9b81f7Schristos  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
139d9b81f7Schristos  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
149d9b81f7Schristos  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
159d9b81f7Schristos  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
169d9b81f7Schristos  */
179d9b81f7Schristos #include <sys/types.h>
189d9b81f7Schristos 
199d9b81f7Schristos #include <assert.h>
209d9b81f7Schristos #include <ctype.h>
219d9b81f7Schristos #include <stdio.h>
22*fc3ee6fdSchristos #include <stdlib.h>
239d9b81f7Schristos #include <string.h>
249d9b81f7Schristos 
259d9b81f7Schristos #include "mandoc_aux.h"
269d9b81f7Schristos #include "mandoc.h"
279d9b81f7Schristos #include "roff.h"
289d9b81f7Schristos #include "mdoc.h"
299d9b81f7Schristos #include "main.h"
309d9b81f7Schristos 
319d9b81f7Schristos struct	md_act {
329d9b81f7Schristos 	int		(*cond)(struct roff_node *n);
339d9b81f7Schristos 	int		(*pre)(struct roff_node *n);
349d9b81f7Schristos 	void		(*post)(struct roff_node *n);
359d9b81f7Schristos 	const char	 *prefix; /* pre-node string constant */
369d9b81f7Schristos 	const char	 *suffix; /* post-node string constant */
379d9b81f7Schristos };
389d9b81f7Schristos 
399d9b81f7Schristos static	void	 md_nodelist(struct roff_node *);
409d9b81f7Schristos static	void	 md_node(struct roff_node *);
419d9b81f7Schristos static	const char *md_stack(char c);
429d9b81f7Schristos static	void	 md_preword(void);
439d9b81f7Schristos static	void	 md_rawword(const char *);
449d9b81f7Schristos static	void	 md_word(const char *);
459d9b81f7Schristos static	void	 md_named(const char *);
469d9b81f7Schristos static	void	 md_char(unsigned char);
479d9b81f7Schristos static	void	 md_uri(const char *);
489d9b81f7Schristos 
499d9b81f7Schristos static	int	 md_cond_head(struct roff_node *);
509d9b81f7Schristos static	int	 md_cond_body(struct roff_node *);
519d9b81f7Schristos 
52*fc3ee6fdSchristos static	int	 md_pre_abort(struct roff_node *);
539d9b81f7Schristos static	int	 md_pre_raw(struct roff_node *);
549d9b81f7Schristos static	int	 md_pre_word(struct roff_node *);
559d9b81f7Schristos static	int	 md_pre_skip(struct roff_node *);
569d9b81f7Schristos static	void	 md_pre_syn(struct roff_node *);
579d9b81f7Schristos static	int	 md_pre_An(struct roff_node *);
589d9b81f7Schristos static	int	 md_pre_Ap(struct roff_node *);
599d9b81f7Schristos static	int	 md_pre_Bd(struct roff_node *);
609d9b81f7Schristos static	int	 md_pre_Bk(struct roff_node *);
619d9b81f7Schristos static	int	 md_pre_Bl(struct roff_node *);
629d9b81f7Schristos static	int	 md_pre_D1(struct roff_node *);
639d9b81f7Schristos static	int	 md_pre_Dl(struct roff_node *);
649d9b81f7Schristos static	int	 md_pre_En(struct roff_node *);
659d9b81f7Schristos static	int	 md_pre_Eo(struct roff_node *);
669d9b81f7Schristos static	int	 md_pre_Fa(struct roff_node *);
679d9b81f7Schristos static	int	 md_pre_Fd(struct roff_node *);
689d9b81f7Schristos static	int	 md_pre_Fn(struct roff_node *);
699d9b81f7Schristos static	int	 md_pre_Fo(struct roff_node *);
709d9b81f7Schristos static	int	 md_pre_In(struct roff_node *);
719d9b81f7Schristos static	int	 md_pre_It(struct roff_node *);
729d9b81f7Schristos static	int	 md_pre_Lk(struct roff_node *);
739d9b81f7Schristos static	int	 md_pre_Mt(struct roff_node *);
749d9b81f7Schristos static	int	 md_pre_Nd(struct roff_node *);
759d9b81f7Schristos static	int	 md_pre_Nm(struct roff_node *);
769d9b81f7Schristos static	int	 md_pre_No(struct roff_node *);
779d9b81f7Schristos static	int	 md_pre_Ns(struct roff_node *);
789d9b81f7Schristos static	int	 md_pre_Pp(struct roff_node *);
799d9b81f7Schristos static	int	 md_pre_Rs(struct roff_node *);
809d9b81f7Schristos static	int	 md_pre_Sh(struct roff_node *);
819d9b81f7Schristos static	int	 md_pre_Sm(struct roff_node *);
829d9b81f7Schristos static	int	 md_pre_Vt(struct roff_node *);
839d9b81f7Schristos static	int	 md_pre_Xr(struct roff_node *);
849d9b81f7Schristos static	int	 md_pre__T(struct roff_node *);
859d9b81f7Schristos static	int	 md_pre_br(struct roff_node *);
869d9b81f7Schristos 
879d9b81f7Schristos static	void	 md_post_raw(struct roff_node *);
889d9b81f7Schristos static	void	 md_post_word(struct roff_node *);
899d9b81f7Schristos static	void	 md_post_pc(struct roff_node *);
909d9b81f7Schristos static	void	 md_post_Bk(struct roff_node *);
919d9b81f7Schristos static	void	 md_post_Bl(struct roff_node *);
929d9b81f7Schristos static	void	 md_post_D1(struct roff_node *);
939d9b81f7Schristos static	void	 md_post_En(struct roff_node *);
949d9b81f7Schristos static	void	 md_post_Eo(struct roff_node *);
959d9b81f7Schristos static	void	 md_post_Fa(struct roff_node *);
969d9b81f7Schristos static	void	 md_post_Fd(struct roff_node *);
979d9b81f7Schristos static	void	 md_post_Fl(struct roff_node *);
989d9b81f7Schristos static	void	 md_post_Fn(struct roff_node *);
999d9b81f7Schristos static	void	 md_post_Fo(struct roff_node *);
1009d9b81f7Schristos static	void	 md_post_In(struct roff_node *);
1019d9b81f7Schristos static	void	 md_post_It(struct roff_node *);
1029d9b81f7Schristos static	void	 md_post_Lb(struct roff_node *);
1039d9b81f7Schristos static	void	 md_post_Nm(struct roff_node *);
1049d9b81f7Schristos static	void	 md_post_Pf(struct roff_node *);
1059d9b81f7Schristos static	void	 md_post_Vt(struct roff_node *);
1069d9b81f7Schristos static	void	 md_post__T(struct roff_node *);
1079d9b81f7Schristos 
108*fc3ee6fdSchristos static	const struct md_act md_acts[MDOC_MAX - MDOC_Dd] = {
1099d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Dd */
1109d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Dt */
1119d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Os */
1129d9b81f7Schristos 	{ NULL, md_pre_Sh, NULL, NULL, NULL }, /* Sh */
1139d9b81f7Schristos 	{ NULL, md_pre_Sh, NULL, NULL, NULL }, /* Ss */
1149d9b81f7Schristos 	{ NULL, md_pre_Pp, NULL, NULL, NULL }, /* Pp */
1159d9b81f7Schristos 	{ md_cond_body, md_pre_D1, md_post_D1, NULL, NULL }, /* D1 */
1169d9b81f7Schristos 	{ md_cond_body, md_pre_Dl, md_post_D1, NULL, NULL }, /* Dl */
1179d9b81f7Schristos 	{ md_cond_body, md_pre_Bd, md_post_D1, NULL, NULL }, /* Bd */
1189d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Ed */
1199d9b81f7Schristos 	{ md_cond_body, md_pre_Bl, md_post_Bl, NULL, NULL }, /* Bl */
1209d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* El */
1219d9b81f7Schristos 	{ NULL, md_pre_It, md_post_It, NULL, NULL }, /* It */
1229d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ad */
1239d9b81f7Schristos 	{ NULL, md_pre_An, NULL, NULL, NULL }, /* An */
1249d9b81f7Schristos 	{ NULL, md_pre_Ap, NULL, NULL, NULL }, /* Ap */
1259d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ar */
1269d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cd */
1279d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cm */
1289d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Dv */
1299d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Er */
1309d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Ev */
1319d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Ex */
1329d9b81f7Schristos 	{ NULL, md_pre_Fa, md_post_Fa, NULL, NULL }, /* Fa */
1339d9b81f7Schristos 	{ NULL, md_pre_Fd, md_post_Fd, "**", "**" }, /* Fd */
1349d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_Fl, "**-", "**" }, /* Fl */
1359d9b81f7Schristos 	{ NULL, md_pre_Fn, md_post_Fn, NULL, NULL }, /* Fn */
1369d9b81f7Schristos 	{ NULL, md_pre_Fd, md_post_raw, "*", "*" }, /* Ft */
1379d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ic */
1389d9b81f7Schristos 	{ NULL, md_pre_In, md_post_In, NULL, NULL }, /* In */
1399d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Li */
1409d9b81f7Schristos 	{ md_cond_head, md_pre_Nd, NULL, NULL, NULL }, /* Nd */
1419d9b81f7Schristos 	{ NULL, md_pre_Nm, md_post_Nm, "**", "**" }, /* Nm */
1429d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Op */
143*fc3ee6fdSchristos 	{ NULL, md_pre_abort, NULL, NULL, NULL }, /* Ot */
1449d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Pa */
1459d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Rv */
1469d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* St */
1479d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Va */
1489d9b81f7Schristos 	{ NULL, md_pre_Vt, md_post_Vt, "*", "*" }, /* Vt */
1499d9b81f7Schristos 	{ NULL, md_pre_Xr, NULL, NULL, NULL }, /* Xr */
1509d9b81f7Schristos 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %A */
1519d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %B */
1529d9b81f7Schristos 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %D */
1539d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %I */
1549d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %J */
1559d9b81f7Schristos 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %N */
1569d9b81f7Schristos 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %O */
1579d9b81f7Schristos 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %P */
1589d9b81f7Schristos 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %R */
1599d9b81f7Schristos 	{ NULL, md_pre__T, md_post__T, NULL, NULL }, /* %T */
1609d9b81f7Schristos 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %V */
1619d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Ac */
1629d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Ao */
1639d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Aq */
1649d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* At */
1659d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Bc */
1669d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Bf XXX not implemented */
1679d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bo */
1689d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bq */
1699d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Bsx */
1709d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Bx */
1719d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Db */
1729d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Dc */
1739d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Do */
1749d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Dq */
1759d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Ec */
1769d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Ef */
1779d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Em */
1789d9b81f7Schristos 	{ md_cond_body, md_pre_Eo, md_post_Eo, NULL, NULL }, /* Eo */
1799d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Fx */
1809d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ms */
1819d9b81f7Schristos 	{ NULL, md_pre_No, NULL, NULL, NULL }, /* No */
1829d9b81f7Schristos 	{ NULL, md_pre_Ns, NULL, NULL, NULL }, /* Ns */
1839d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Nx */
1849d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Ox */
1859d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Pc */
1869d9b81f7Schristos 	{ NULL, NULL, md_post_Pf, NULL, NULL }, /* Pf */
1879d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Po */
1889d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Pq */
1899d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Qc */
1909d9b81f7Schristos 	{ md_cond_body, md_pre_raw, md_post_raw, "'`", "`'" }, /* Ql */
1919d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qo */
1929d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qq */
1939d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Re */
1949d9b81f7Schristos 	{ md_cond_body, md_pre_Rs, NULL, NULL, NULL }, /* Rs */
1959d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Sc */
1969d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* So */
1979d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* Sq */
1989d9b81f7Schristos 	{ NULL, md_pre_Sm, NULL, NULL, NULL }, /* Sm */
1999d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Sx */
2009d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Sy */
2019d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Tn */
2029d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Ux */
2039d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Xc */
2049d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Xo */
2059d9b81f7Schristos 	{ NULL, md_pre_Fo, md_post_Fo, "**", "**" }, /* Fo */
2069d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Fc */
2079d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Oo */
2089d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Oc */
2099d9b81f7Schristos 	{ NULL, md_pre_Bk, md_post_Bk, NULL, NULL }, /* Bk */
2109d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Ek */
2119d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Bt */
2129d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Hf */
2139d9b81f7Schristos 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Fr */
2149d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Ud */
2159d9b81f7Schristos 	{ NULL, NULL, md_post_Lb, NULL, NULL }, /* Lb */
216*fc3ee6fdSchristos 	{ NULL, md_pre_abort, NULL, NULL, NULL }, /* Lp */
2179d9b81f7Schristos 	{ NULL, md_pre_Lk, NULL, NULL, NULL }, /* Lk */
2189d9b81f7Schristos 	{ NULL, md_pre_Mt, NULL, NULL, NULL }, /* Mt */
2199d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Brq */
2209d9b81f7Schristos 	{ md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Bro */
2219d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Brc */
2229d9b81f7Schristos 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %C */
2239d9b81f7Schristos 	{ NULL, md_pre_skip, NULL, NULL, NULL }, /* Es */
2249d9b81f7Schristos 	{ md_cond_body, md_pre_En, md_post_En, NULL, NULL }, /* En */
2259d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Dx */
2269d9b81f7Schristos 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %Q */
2279d9b81f7Schristos 	{ NULL, md_pre_Lk, md_post_pc, NULL, NULL }, /* %U */
2289d9b81f7Schristos 	{ NULL, NULL, NULL, NULL, NULL }, /* Ta */
2299d9b81f7Schristos };
230*fc3ee6fdSchristos static const struct md_act *md_act(enum roff_tok);
2319d9b81f7Schristos 
2329d9b81f7Schristos static	int	 outflags;
2339d9b81f7Schristos #define	MD_spc		 (1 << 0)  /* Blank character before next word. */
2349d9b81f7Schristos #define	MD_spc_force	 (1 << 1)  /* Even before trailing punctuation. */
2359d9b81f7Schristos #define	MD_nonl		 (1 << 2)  /* Prevent linebreak in markdown code. */
2369d9b81f7Schristos #define	MD_nl		 (1 << 3)  /* Break markdown code line. */
2379d9b81f7Schristos #define	MD_br		 (1 << 4)  /* Insert an output line break. */
2389d9b81f7Schristos #define	MD_sp		 (1 << 5)  /* Insert a paragraph break. */
2399d9b81f7Schristos #define	MD_Sm		 (1 << 6)  /* Horizontal spacing mode. */
2409d9b81f7Schristos #define	MD_Bk		 (1 << 7)  /* Word keep mode. */
2419d9b81f7Schristos #define	MD_An_split	 (1 << 8)  /* Author mode is "split". */
2429d9b81f7Schristos #define	MD_An_nosplit	 (1 << 9)  /* Author mode is "nosplit". */
2439d9b81f7Schristos 
2449d9b81f7Schristos static	int	 escflags; /* Escape in generated markdown code: */
2459d9b81f7Schristos #define	ESC_BOL	 (1 << 0)  /* "#*+-" near the beginning of a line. */
2469d9b81f7Schristos #define	ESC_NUM	 (1 << 1)  /* "." after a leading number. */
2479d9b81f7Schristos #define	ESC_HYP	 (1 << 2)  /* "(" immediately after "]". */
2489d9b81f7Schristos #define	ESC_SQU	 (1 << 4)  /* "]" when "[" is open. */
2499d9b81f7Schristos #define	ESC_FON	 (1 << 5)  /* "*" immediately after unrelated "*". */
2509d9b81f7Schristos #define	ESC_EOL	 (1 << 6)  /* " " at the and of a line. */
2519d9b81f7Schristos 
2529d9b81f7Schristos static	int	 code_blocks, quote_blocks, list_blocks;
2539d9b81f7Schristos static	int	 outcount;
2549d9b81f7Schristos 
255*fc3ee6fdSchristos 
256*fc3ee6fdSchristos static const struct md_act *
md_act(enum roff_tok tok)257*fc3ee6fdSchristos md_act(enum roff_tok tok)
258*fc3ee6fdSchristos {
259*fc3ee6fdSchristos 	assert(tok >= MDOC_Dd && tok <= MDOC_MAX);
260*fc3ee6fdSchristos 	return md_acts + (tok - MDOC_Dd);
261*fc3ee6fdSchristos }
262*fc3ee6fdSchristos 
2639d9b81f7Schristos void
markdown_mdoc(void * arg,const struct roff_meta * mdoc)264*fc3ee6fdSchristos markdown_mdoc(void *arg, const struct roff_meta *mdoc)
2659d9b81f7Schristos {
2669d9b81f7Schristos 	outflags = MD_Sm;
267*fc3ee6fdSchristos 	md_word(mdoc->title);
268*fc3ee6fdSchristos 	if (mdoc->msec != NULL) {
2699d9b81f7Schristos 		outflags &= ~MD_spc;
2709d9b81f7Schristos 		md_word("(");
271*fc3ee6fdSchristos 		md_word(mdoc->msec);
2729d9b81f7Schristos 		md_word(")");
2739d9b81f7Schristos 	}
2749d9b81f7Schristos 	md_word("-");
275*fc3ee6fdSchristos 	md_word(mdoc->vol);
276*fc3ee6fdSchristos 	if (mdoc->arch != NULL) {
2779d9b81f7Schristos 		md_word("(");
278*fc3ee6fdSchristos 		md_word(mdoc->arch);
2799d9b81f7Schristos 		md_word(")");
2809d9b81f7Schristos 	}
2819d9b81f7Schristos 	outflags |= MD_sp;
2829d9b81f7Schristos 
2839d9b81f7Schristos 	md_nodelist(mdoc->first->child);
2849d9b81f7Schristos 
2859d9b81f7Schristos 	outflags |= MD_sp;
286*fc3ee6fdSchristos 	md_word(mdoc->os);
2879d9b81f7Schristos 	md_word("-");
288*fc3ee6fdSchristos 	md_word(mdoc->date);
2899d9b81f7Schristos 	putchar('\n');
2909d9b81f7Schristos }
2919d9b81f7Schristos 
2929d9b81f7Schristos static void
md_nodelist(struct roff_node * n)2939d9b81f7Schristos md_nodelist(struct roff_node *n)
2949d9b81f7Schristos {
2959d9b81f7Schristos 	while (n != NULL) {
2969d9b81f7Schristos 		md_node(n);
2979d9b81f7Schristos 		n = n->next;
2989d9b81f7Schristos 	}
2999d9b81f7Schristos }
3009d9b81f7Schristos 
3019d9b81f7Schristos static void
md_node(struct roff_node * n)3029d9b81f7Schristos md_node(struct roff_node *n)
3039d9b81f7Schristos {
3049d9b81f7Schristos 	const struct md_act	*act;
3059d9b81f7Schristos 	int			 cond, process_children;
3069d9b81f7Schristos 
3079d9b81f7Schristos 	if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
3089d9b81f7Schristos 		return;
3099d9b81f7Schristos 
3109d9b81f7Schristos 	if (outflags & MD_nonl)
3119d9b81f7Schristos 		outflags &= ~(MD_nl | MD_sp);
3129d9b81f7Schristos 	else if (outflags & MD_spc && n->flags & NODE_LINE)
3139d9b81f7Schristos 		outflags |= MD_nl;
3149d9b81f7Schristos 
3159d9b81f7Schristos 	act = NULL;
3169d9b81f7Schristos 	cond = 0;
3179d9b81f7Schristos 	process_children = 1;
3189d9b81f7Schristos 	n->flags &= ~NODE_ENDED;
3199d9b81f7Schristos 
3209d9b81f7Schristos 	if (n->type == ROFFT_TEXT) {
3219d9b81f7Schristos 		if (n->flags & NODE_DELIMC)
3229d9b81f7Schristos 			outflags &= ~(MD_spc | MD_spc_force);
3239d9b81f7Schristos 		else if (outflags & MD_Sm)
3249d9b81f7Schristos 			outflags |= MD_spc_force;
3259d9b81f7Schristos 		md_word(n->string);
3269d9b81f7Schristos 		if (n->flags & NODE_DELIMO)
3279d9b81f7Schristos 			outflags &= ~(MD_spc | MD_spc_force);
3289d9b81f7Schristos 		else if (outflags & MD_Sm)
3299d9b81f7Schristos 			outflags |= MD_spc;
3309d9b81f7Schristos 	} else if (n->tok < ROFF_MAX) {
3319d9b81f7Schristos 		switch (n->tok) {
3329d9b81f7Schristos 		case ROFF_br:
3339d9b81f7Schristos 			process_children = md_pre_br(n);
3349d9b81f7Schristos 			break;
3359d9b81f7Schristos 		case ROFF_sp:
3369d9b81f7Schristos 			process_children = md_pre_Pp(n);
3379d9b81f7Schristos 			break;
3389d9b81f7Schristos 		default:
3399d9b81f7Schristos 			process_children = 0;
3409d9b81f7Schristos 			break;
3419d9b81f7Schristos 		}
3429d9b81f7Schristos 	} else {
343*fc3ee6fdSchristos 		act = md_act(n->tok);
3449d9b81f7Schristos 		cond = act->cond == NULL || (*act->cond)(n);
3459d9b81f7Schristos 		if (cond && act->pre != NULL &&
3469d9b81f7Schristos 		    (n->end == ENDBODY_NOT || n->child != NULL))
3479d9b81f7Schristos 			process_children = (*act->pre)(n);
3489d9b81f7Schristos 	}
3499d9b81f7Schristos 
3509d9b81f7Schristos 	if (process_children && n->child != NULL)
3519d9b81f7Schristos 		md_nodelist(n->child);
3529d9b81f7Schristos 
3539d9b81f7Schristos 	if (n->flags & NODE_ENDED)
3549d9b81f7Schristos 		return;
3559d9b81f7Schristos 
3569d9b81f7Schristos 	if (cond && act->post != NULL)
3579d9b81f7Schristos 		(*act->post)(n);
3589d9b81f7Schristos 
3599d9b81f7Schristos 	if (n->end != ENDBODY_NOT)
3609d9b81f7Schristos 		n->body->flags |= NODE_ENDED;
3619d9b81f7Schristos }
3629d9b81f7Schristos 
3639d9b81f7Schristos static const char *
md_stack(char c)3649d9b81f7Schristos md_stack(char c)
3659d9b81f7Schristos {
3669d9b81f7Schristos 	static char	*stack;
3679d9b81f7Schristos 	static size_t	 sz;
3689d9b81f7Schristos 	static size_t	 cur;
3699d9b81f7Schristos 
3709d9b81f7Schristos 	switch (c) {
3719d9b81f7Schristos 	case '\0':
3729d9b81f7Schristos 		break;
3739d9b81f7Schristos 	case (char)-1:
3749d9b81f7Schristos 		assert(cur);
3759d9b81f7Schristos 		stack[--cur] = '\0';
3769d9b81f7Schristos 		break;
3779d9b81f7Schristos 	default:
3789d9b81f7Schristos 		if (cur + 1 >= sz) {
3799d9b81f7Schristos 			sz += 8;
3809d9b81f7Schristos 			stack = mandoc_realloc(stack, sz);
3819d9b81f7Schristos 		}
3829d9b81f7Schristos 		stack[cur] = c;
3839d9b81f7Schristos 		stack[++cur] = '\0';
3849d9b81f7Schristos 		break;
3859d9b81f7Schristos 	}
3869d9b81f7Schristos 	return stack == NULL ? "" : stack;
3879d9b81f7Schristos }
3889d9b81f7Schristos 
3899d9b81f7Schristos /*
3909d9b81f7Schristos  * Handle vertical and horizontal spacing.
3919d9b81f7Schristos  */
3929d9b81f7Schristos static void
md_preword(void)3939d9b81f7Schristos md_preword(void)
3949d9b81f7Schristos {
3959d9b81f7Schristos 	const char	*cp;
3969d9b81f7Schristos 
3979d9b81f7Schristos 	/*
3989d9b81f7Schristos 	 * If a list block is nested inside a code block or a blockquote,
3999d9b81f7Schristos 	 * blank lines for paragraph breaks no longer work; instead,
4009d9b81f7Schristos 	 * they terminate the list.  Work around this markdown issue
4019d9b81f7Schristos 	 * by using mere line breaks instead.
4029d9b81f7Schristos 	 */
4039d9b81f7Schristos 
4049d9b81f7Schristos 	if (list_blocks && outflags & MD_sp) {
4059d9b81f7Schristos 		outflags &= ~MD_sp;
4069d9b81f7Schristos 		outflags |= MD_br;
4079d9b81f7Schristos 	}
4089d9b81f7Schristos 
4099d9b81f7Schristos 	/*
4109d9b81f7Schristos 	 * End the old line if requested.
4119d9b81f7Schristos 	 * Escape whitespace at the end of the markdown line
4129d9b81f7Schristos 	 * such that it won't look like an output line break.
4139d9b81f7Schristos 	 */
4149d9b81f7Schristos 
4159d9b81f7Schristos 	if (outflags & MD_sp)
4169d9b81f7Schristos 		putchar('\n');
4179d9b81f7Schristos 	else if (outflags & MD_br) {
4189d9b81f7Schristos 		putchar(' ');
4199d9b81f7Schristos 		putchar(' ');
4209d9b81f7Schristos 	} else if (outflags & MD_nl && escflags & ESC_EOL)
4219d9b81f7Schristos 		md_named("zwnj");
4229d9b81f7Schristos 
4239d9b81f7Schristos 	/* Start a new line if necessary. */
4249d9b81f7Schristos 
4259d9b81f7Schristos 	if (outflags & (MD_nl | MD_br | MD_sp)) {
4269d9b81f7Schristos 		putchar('\n');
4279d9b81f7Schristos 		for (cp = md_stack('\0'); *cp != '\0'; cp++) {
4289d9b81f7Schristos 			putchar(*cp);
4299d9b81f7Schristos 			if (*cp == '>')
4309d9b81f7Schristos 				putchar(' ');
4319d9b81f7Schristos 		}
4329d9b81f7Schristos 		outflags &= ~(MD_nl | MD_br | MD_sp);
4339d9b81f7Schristos 		escflags = ESC_BOL;
4349d9b81f7Schristos 		outcount = 0;
4359d9b81f7Schristos 
4369d9b81f7Schristos 	/* Handle horizontal spacing. */
4379d9b81f7Schristos 
4389d9b81f7Schristos 	} else if (outflags & MD_spc) {
4399d9b81f7Schristos 		if (outflags & MD_Bk)
4409d9b81f7Schristos 			fputs("&nbsp;", stdout);
4419d9b81f7Schristos 		else
4429d9b81f7Schristos 			putchar(' ');
4439d9b81f7Schristos 		escflags &= ~ESC_FON;
4449d9b81f7Schristos 		outcount++;
4459d9b81f7Schristos 	}
4469d9b81f7Schristos 
4479d9b81f7Schristos 	outflags &= ~(MD_spc_force | MD_nonl);
4489d9b81f7Schristos 	if (outflags & MD_Sm)
4499d9b81f7Schristos 		outflags |= MD_spc;
4509d9b81f7Schristos 	else
4519d9b81f7Schristos 		outflags &= ~MD_spc;
4529d9b81f7Schristos }
4539d9b81f7Schristos 
4549d9b81f7Schristos /*
4559d9b81f7Schristos  * Print markdown syntax elements.
4569d9b81f7Schristos  * Can also be used for constant strings when neither escaping
4579d9b81f7Schristos  * nor delimiter handling is required.
4589d9b81f7Schristos  */
4599d9b81f7Schristos static void
md_rawword(const char * s)4609d9b81f7Schristos md_rawword(const char *s)
4619d9b81f7Schristos {
4629d9b81f7Schristos 	md_preword();
4639d9b81f7Schristos 
4649d9b81f7Schristos 	if (*s == '\0')
4659d9b81f7Schristos 		return;
4669d9b81f7Schristos 
4679d9b81f7Schristos 	if (escflags & ESC_FON) {
4689d9b81f7Schristos 		escflags &= ~ESC_FON;
4699d9b81f7Schristos 		if (*s == '*' && !code_blocks)
4709d9b81f7Schristos 			fputs("&zwnj;", stdout);
4719d9b81f7Schristos 	}
4729d9b81f7Schristos 
4739d9b81f7Schristos 	while (*s != '\0') {
4749d9b81f7Schristos 		switch(*s) {
4759d9b81f7Schristos 		case '*':
4769d9b81f7Schristos 			if (s[1] == '\0')
4779d9b81f7Schristos 				escflags |= ESC_FON;
4789d9b81f7Schristos 			break;
4799d9b81f7Schristos 		case '[':
4809d9b81f7Schristos 			escflags |= ESC_SQU;
4819d9b81f7Schristos 			break;
4829d9b81f7Schristos 		case ']':
4839d9b81f7Schristos 			escflags |= ESC_HYP;
4849d9b81f7Schristos 			escflags &= ~ESC_SQU;
4859d9b81f7Schristos 			break;
4869d9b81f7Schristos 		default:
4879d9b81f7Schristos 			break;
4889d9b81f7Schristos 		}
4899d9b81f7Schristos 		md_char(*s++);
4909d9b81f7Schristos 	}
4919d9b81f7Schristos 	if (s[-1] == ' ')
4929d9b81f7Schristos 		escflags |= ESC_EOL;
4939d9b81f7Schristos 	else
4949d9b81f7Schristos 		escflags &= ~ESC_EOL;
4959d9b81f7Schristos }
4969d9b81f7Schristos 
4979d9b81f7Schristos /*
4989d9b81f7Schristos  * Print text and mdoc(7) syntax elements.
4999d9b81f7Schristos  */
5009d9b81f7Schristos static void
md_word(const char * s)5019d9b81f7Schristos md_word(const char *s)
5029d9b81f7Schristos {
5039d9b81f7Schristos 	const char	*seq, *prevfont, *currfont, *nextfont;
5049d9b81f7Schristos 	char		 c;
5059d9b81f7Schristos 	int		 bs, sz, uc, breakline;
5069d9b81f7Schristos 
5079d9b81f7Schristos 	/* No spacing before closing delimiters. */
5089d9b81f7Schristos 	if (s[0] != '\0' && s[1] == '\0' &&
5099d9b81f7Schristos 	    strchr("!),.:;?]", s[0]) != NULL &&
5109d9b81f7Schristos 	    (outflags & MD_spc_force) == 0)
5119d9b81f7Schristos 		outflags &= ~MD_spc;
5129d9b81f7Schristos 
5139d9b81f7Schristos 	md_preword();
5149d9b81f7Schristos 
5159d9b81f7Schristos 	if (*s == '\0')
5169d9b81f7Schristos 		return;
5179d9b81f7Schristos 
5189d9b81f7Schristos 	/* No spacing after opening delimiters. */
5199d9b81f7Schristos 	if ((s[0] == '(' || s[0] == '[') && s[1] == '\0')
5209d9b81f7Schristos 		outflags &= ~MD_spc;
5219d9b81f7Schristos 
5229d9b81f7Schristos 	breakline = 0;
5239d9b81f7Schristos 	prevfont = currfont = "";
5249d9b81f7Schristos 	while ((c = *s++) != '\0') {
5259d9b81f7Schristos 		bs = 0;
5269d9b81f7Schristos 		switch(c) {
5279d9b81f7Schristos 		case ASCII_NBRSP:
5289d9b81f7Schristos 			if (code_blocks)
5299d9b81f7Schristos 				c = ' ';
5309d9b81f7Schristos 			else {
5319d9b81f7Schristos 				md_named("nbsp");
5329d9b81f7Schristos 				c = '\0';
5339d9b81f7Schristos 			}
5349d9b81f7Schristos 			break;
5359d9b81f7Schristos 		case ASCII_HYPH:
5369d9b81f7Schristos 			bs = escflags & ESC_BOL && !code_blocks;
5379d9b81f7Schristos 			c = '-';
5389d9b81f7Schristos 			break;
5399d9b81f7Schristos 		case ASCII_BREAK:
5409d9b81f7Schristos 			continue;
5419d9b81f7Schristos 		case '#':
5429d9b81f7Schristos 		case '+':
5439d9b81f7Schristos 		case '-':
5449d9b81f7Schristos 			bs = escflags & ESC_BOL && !code_blocks;
5459d9b81f7Schristos 			break;
5469d9b81f7Schristos 		case '(':
5479d9b81f7Schristos 			bs = escflags & ESC_HYP && !code_blocks;
5489d9b81f7Schristos 			break;
5499d9b81f7Schristos 		case ')':
5509d9b81f7Schristos 			bs = escflags & ESC_NUM && !code_blocks;
5519d9b81f7Schristos 			break;
5529d9b81f7Schristos 		case '*':
5539d9b81f7Schristos 		case '[':
5549d9b81f7Schristos 		case '_':
5559d9b81f7Schristos 		case '`':
5569d9b81f7Schristos 			bs = !code_blocks;
5579d9b81f7Schristos 			break;
5589d9b81f7Schristos 		case '.':
5599d9b81f7Schristos 			bs = escflags & ESC_NUM && !code_blocks;
5609d9b81f7Schristos 			break;
5619d9b81f7Schristos 		case '<':
5629d9b81f7Schristos 			if (code_blocks == 0) {
5639d9b81f7Schristos 				md_named("lt");
5649d9b81f7Schristos 				c = '\0';
5659d9b81f7Schristos 			}
5669d9b81f7Schristos 			break;
5679d9b81f7Schristos 		case '=':
5689d9b81f7Schristos 			if (escflags & ESC_BOL && !code_blocks) {
5699d9b81f7Schristos 				md_named("equals");
5709d9b81f7Schristos 				c = '\0';
5719d9b81f7Schristos 			}
5729d9b81f7Schristos 			break;
5739d9b81f7Schristos 		case '>':
5749d9b81f7Schristos 			if (code_blocks == 0) {
5759d9b81f7Schristos 				md_named("gt");
5769d9b81f7Schristos 				c = '\0';
5779d9b81f7Schristos 			}
5789d9b81f7Schristos 			break;
5799d9b81f7Schristos 		case '\\':
5809d9b81f7Schristos 			uc = 0;
5819d9b81f7Schristos 			nextfont = NULL;
5829d9b81f7Schristos 			switch (mandoc_escape(&s, &seq, &sz)) {
5839d9b81f7Schristos 			case ESCAPE_UNICODE:
5849d9b81f7Schristos 				uc = mchars_num2uc(seq + 1, sz - 1);
5859d9b81f7Schristos 				break;
5869d9b81f7Schristos 			case ESCAPE_NUMBERED:
5879d9b81f7Schristos 				uc = mchars_num2char(seq, sz);
5889d9b81f7Schristos 				break;
5899d9b81f7Schristos 			case ESCAPE_SPECIAL:
5909d9b81f7Schristos 				uc = mchars_spec2cp(seq, sz);
5919d9b81f7Schristos 				break;
592*fc3ee6fdSchristos 			case ESCAPE_UNDEF:
593*fc3ee6fdSchristos 				uc = *seq;
594*fc3ee6fdSchristos 				break;
595*fc3ee6fdSchristos 			case ESCAPE_DEVICE:
596*fc3ee6fdSchristos 				md_rawword("markdown");
597*fc3ee6fdSchristos 				continue;
5989d9b81f7Schristos 			case ESCAPE_FONTBOLD:
5999d9b81f7Schristos 				nextfont = "**";
6009d9b81f7Schristos 				break;
6019d9b81f7Schristos 			case ESCAPE_FONTITALIC:
6029d9b81f7Schristos 				nextfont = "*";
6039d9b81f7Schristos 				break;
6049d9b81f7Schristos 			case ESCAPE_FONTBI:
6059d9b81f7Schristos 				nextfont = "***";
6069d9b81f7Schristos 				break;
6079d9b81f7Schristos 			case ESCAPE_FONT:
608*fc3ee6fdSchristos 			case ESCAPE_FONTCW:
6099d9b81f7Schristos 			case ESCAPE_FONTROMAN:
6109d9b81f7Schristos 				nextfont = "";
6119d9b81f7Schristos 				break;
6129d9b81f7Schristos 			case ESCAPE_FONTPREV:
6139d9b81f7Schristos 				nextfont = prevfont;
6149d9b81f7Schristos 				break;
6159d9b81f7Schristos 			case ESCAPE_BREAK:
6169d9b81f7Schristos 				breakline = 1;
6179d9b81f7Schristos 				break;
6189d9b81f7Schristos 			case ESCAPE_NOSPACE:
6199d9b81f7Schristos 			case ESCAPE_SKIPCHAR:
6209d9b81f7Schristos 			case ESCAPE_OVERSTRIKE:
6219d9b81f7Schristos 				/* XXX not implemented */
6229d9b81f7Schristos 				/* FALLTHROUGH */
6239d9b81f7Schristos 			case ESCAPE_ERROR:
6249d9b81f7Schristos 			default:
6259d9b81f7Schristos 				break;
6269d9b81f7Schristos 			}
6279d9b81f7Schristos 			if (nextfont != NULL && !code_blocks) {
6289d9b81f7Schristos 				if (*currfont != '\0') {
6299d9b81f7Schristos 					outflags &= ~MD_spc;
6309d9b81f7Schristos 					md_rawword(currfont);
6319d9b81f7Schristos 				}
6329d9b81f7Schristos 				prevfont = currfont;
6339d9b81f7Schristos 				currfont = nextfont;
6349d9b81f7Schristos 				if (*currfont != '\0') {
6359d9b81f7Schristos 					outflags &= ~MD_spc;
6369d9b81f7Schristos 					md_rawword(currfont);
6379d9b81f7Schristos 				}
6389d9b81f7Schristos 			}
6399d9b81f7Schristos 			if (uc) {
6409d9b81f7Schristos 				if ((uc < 0x20 && uc != 0x09) ||
6419d9b81f7Schristos 				    (uc > 0x7E && uc < 0xA0))
6429d9b81f7Schristos 					uc = 0xFFFD;
6439d9b81f7Schristos 				if (code_blocks) {
6449d9b81f7Schristos 					seq = mchars_uc2str(uc);
6459d9b81f7Schristos 					fputs(seq, stdout);
6469d9b81f7Schristos 					outcount += strlen(seq);
6479d9b81f7Schristos 				} else {
6489d9b81f7Schristos 					printf("&#%d;", uc);
6499d9b81f7Schristos 					outcount++;
6509d9b81f7Schristos 				}
6519d9b81f7Schristos 				escflags &= ~ESC_FON;
6529d9b81f7Schristos 			}
6539d9b81f7Schristos 			c = '\0';
6549d9b81f7Schristos 			break;
6559d9b81f7Schristos 		case ']':
6569d9b81f7Schristos 			bs = escflags & ESC_SQU && !code_blocks;
6579d9b81f7Schristos 			escflags |= ESC_HYP;
6589d9b81f7Schristos 			break;
6599d9b81f7Schristos 		default:
6609d9b81f7Schristos 			break;
6619d9b81f7Schristos 		}
6629d9b81f7Schristos 		if (bs)
6639d9b81f7Schristos 			putchar('\\');
6649d9b81f7Schristos 		md_char(c);
6659d9b81f7Schristos 		if (breakline &&
6669d9b81f7Schristos 		    (*s == '\0' || *s == ' ' || *s == ASCII_NBRSP)) {
6679d9b81f7Schristos 			printf("  \n");
6689d9b81f7Schristos 			breakline = 0;
6699d9b81f7Schristos 			while (*s == ' ' || *s == ASCII_NBRSP)
6709d9b81f7Schristos 				s++;
6719d9b81f7Schristos 		}
6729d9b81f7Schristos 	}
6739d9b81f7Schristos 	if (*currfont != '\0') {
6749d9b81f7Schristos 		outflags &= ~MD_spc;
6759d9b81f7Schristos 		md_rawword(currfont);
6769d9b81f7Schristos 	} else if (s[-2] == ' ')
6779d9b81f7Schristos 		escflags |= ESC_EOL;
6789d9b81f7Schristos 	else
6799d9b81f7Schristos 		escflags &= ~ESC_EOL;
6809d9b81f7Schristos }
6819d9b81f7Schristos 
6829d9b81f7Schristos /*
6839d9b81f7Schristos  * Print a single HTML named character reference.
6849d9b81f7Schristos  */
6859d9b81f7Schristos static void
md_named(const char * s)6869d9b81f7Schristos md_named(const char *s)
6879d9b81f7Schristos {
6889d9b81f7Schristos 	printf("&%s;", s);
6899d9b81f7Schristos 	escflags &= ~(ESC_FON | ESC_EOL);
6909d9b81f7Schristos 	outcount++;
6919d9b81f7Schristos }
6929d9b81f7Schristos 
6939d9b81f7Schristos /*
6949d9b81f7Schristos  * Print a single raw character and maintain certain escape flags.
6959d9b81f7Schristos  */
6969d9b81f7Schristos static void
md_char(unsigned char c)6979d9b81f7Schristos md_char(unsigned char c)
6989d9b81f7Schristos {
6999d9b81f7Schristos 	if (c != '\0') {
7009d9b81f7Schristos 		putchar(c);
7019d9b81f7Schristos 		if (c == '*')
7029d9b81f7Schristos 			escflags |= ESC_FON;
7039d9b81f7Schristos 		else
7049d9b81f7Schristos 			escflags &= ~ESC_FON;
7059d9b81f7Schristos 		outcount++;
7069d9b81f7Schristos 	}
7079d9b81f7Schristos 	if (c != ']')
7089d9b81f7Schristos 		escflags &= ~ESC_HYP;
7099d9b81f7Schristos 	if (c == ' ' || c == '\t' || c == '>')
7109d9b81f7Schristos 		return;
7119d9b81f7Schristos 	if (isdigit(c) == 0)
7129d9b81f7Schristos 		escflags &= ~ESC_NUM;
7139d9b81f7Schristos 	else if (escflags & ESC_BOL)
7149d9b81f7Schristos 		escflags |= ESC_NUM;
7159d9b81f7Schristos 	escflags &= ~ESC_BOL;
7169d9b81f7Schristos }
7179d9b81f7Schristos 
7189d9b81f7Schristos static int
md_cond_head(struct roff_node * n)7199d9b81f7Schristos md_cond_head(struct roff_node *n)
7209d9b81f7Schristos {
7219d9b81f7Schristos 	return n->type == ROFFT_HEAD;
7229d9b81f7Schristos }
7239d9b81f7Schristos 
7249d9b81f7Schristos static int
md_cond_body(struct roff_node * n)7259d9b81f7Schristos md_cond_body(struct roff_node *n)
7269d9b81f7Schristos {
7279d9b81f7Schristos 	return n->type == ROFFT_BODY;
7289d9b81f7Schristos }
7299d9b81f7Schristos 
7309d9b81f7Schristos static int
md_pre_abort(struct roff_node * n)731*fc3ee6fdSchristos md_pre_abort(struct roff_node *n)
732*fc3ee6fdSchristos {
733*fc3ee6fdSchristos 	abort();
734*fc3ee6fdSchristos }
735*fc3ee6fdSchristos 
736*fc3ee6fdSchristos static int
md_pre_raw(struct roff_node * n)7379d9b81f7Schristos md_pre_raw(struct roff_node *n)
7389d9b81f7Schristos {
7399d9b81f7Schristos 	const char	*prefix;
7409d9b81f7Schristos 
741*fc3ee6fdSchristos 	if ((prefix = md_act(n->tok)->prefix) != NULL) {
7429d9b81f7Schristos 		md_rawword(prefix);
7439d9b81f7Schristos 		outflags &= ~MD_spc;
7449d9b81f7Schristos 		if (*prefix == '`')
7459d9b81f7Schristos 			code_blocks++;
7469d9b81f7Schristos 	}
7479d9b81f7Schristos 	return 1;
7489d9b81f7Schristos }
7499d9b81f7Schristos 
7509d9b81f7Schristos static void
md_post_raw(struct roff_node * n)7519d9b81f7Schristos md_post_raw(struct roff_node *n)
7529d9b81f7Schristos {
7539d9b81f7Schristos 	const char	*suffix;
7549d9b81f7Schristos 
755*fc3ee6fdSchristos 	if ((suffix = md_act(n->tok)->suffix) != NULL) {
7569d9b81f7Schristos 		outflags &= ~(MD_spc | MD_nl);
7579d9b81f7Schristos 		md_rawword(suffix);
7589d9b81f7Schristos 		if (*suffix == '`')
7599d9b81f7Schristos 			code_blocks--;
7609d9b81f7Schristos 	}
7619d9b81f7Schristos }
7629d9b81f7Schristos 
7639d9b81f7Schristos static int
md_pre_word(struct roff_node * n)7649d9b81f7Schristos md_pre_word(struct roff_node *n)
7659d9b81f7Schristos {
7669d9b81f7Schristos 	const char	*prefix;
7679d9b81f7Schristos 
768*fc3ee6fdSchristos 	if ((prefix = md_act(n->tok)->prefix) != NULL) {
7699d9b81f7Schristos 		md_word(prefix);
7709d9b81f7Schristos 		outflags &= ~MD_spc;
7719d9b81f7Schristos 	}
7729d9b81f7Schristos 	return 1;
7739d9b81f7Schristos }
7749d9b81f7Schristos 
7759d9b81f7Schristos static void
md_post_word(struct roff_node * n)7769d9b81f7Schristos md_post_word(struct roff_node *n)
7779d9b81f7Schristos {
7789d9b81f7Schristos 	const char	*suffix;
7799d9b81f7Schristos 
780*fc3ee6fdSchristos 	if ((suffix = md_act(n->tok)->suffix) != NULL) {
7819d9b81f7Schristos 		outflags &= ~(MD_spc | MD_nl);
7829d9b81f7Schristos 		md_word(suffix);
7839d9b81f7Schristos 	}
7849d9b81f7Schristos }
7859d9b81f7Schristos 
7869d9b81f7Schristos static void
md_post_pc(struct roff_node * n)7879d9b81f7Schristos md_post_pc(struct roff_node *n)
7889d9b81f7Schristos {
7899d9b81f7Schristos 	md_post_raw(n);
7909d9b81f7Schristos 	if (n->parent->tok != MDOC_Rs)
7919d9b81f7Schristos 		return;
7929d9b81f7Schristos 	if (n->next != NULL) {
7939d9b81f7Schristos 		md_word(",");
7949d9b81f7Schristos 		if (n->prev != NULL &&
7959d9b81f7Schristos 		    n->prev->tok == n->tok &&
7969d9b81f7Schristos 		    n->next->tok == n->tok)
7979d9b81f7Schristos 			md_word("and");
7989d9b81f7Schristos 	} else {
7999d9b81f7Schristos 		md_word(".");
8009d9b81f7Schristos 		outflags |= MD_nl;
8019d9b81f7Schristos 	}
8029d9b81f7Schristos }
8039d9b81f7Schristos 
8049d9b81f7Schristos static int
md_pre_skip(struct roff_node * n)8059d9b81f7Schristos md_pre_skip(struct roff_node *n)
8069d9b81f7Schristos {
8079d9b81f7Schristos 	return 0;
8089d9b81f7Schristos }
8099d9b81f7Schristos 
8109d9b81f7Schristos static void
md_pre_syn(struct roff_node * n)8119d9b81f7Schristos md_pre_syn(struct roff_node *n)
8129d9b81f7Schristos {
8139d9b81f7Schristos 	if (n->prev == NULL || ! (n->flags & NODE_SYNPRETTY))
8149d9b81f7Schristos 		return;
8159d9b81f7Schristos 
8169d9b81f7Schristos 	if (n->prev->tok == n->tok &&
8179d9b81f7Schristos 	    n->tok != MDOC_Ft &&
8189d9b81f7Schristos 	    n->tok != MDOC_Fo &&
8199d9b81f7Schristos 	    n->tok != MDOC_Fn) {
8209d9b81f7Schristos 		outflags |= MD_br;
8219d9b81f7Schristos 		return;
8229d9b81f7Schristos 	}
8239d9b81f7Schristos 
8249d9b81f7Schristos 	switch (n->prev->tok) {
8259d9b81f7Schristos 	case MDOC_Fd:
8269d9b81f7Schristos 	case MDOC_Fn:
8279d9b81f7Schristos 	case MDOC_Fo:
8289d9b81f7Schristos 	case MDOC_In:
8299d9b81f7Schristos 	case MDOC_Vt:
8309d9b81f7Schristos 		outflags |= MD_sp;
8319d9b81f7Schristos 		break;
8329d9b81f7Schristos 	case MDOC_Ft:
8339d9b81f7Schristos 		if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) {
8349d9b81f7Schristos 			outflags |= MD_sp;
8359d9b81f7Schristos 			break;
8369d9b81f7Schristos 		}
8379d9b81f7Schristos 		/* FALLTHROUGH */
8389d9b81f7Schristos 	default:
8399d9b81f7Schristos 		outflags |= MD_br;
8409d9b81f7Schristos 		break;
8419d9b81f7Schristos 	}
8429d9b81f7Schristos }
8439d9b81f7Schristos 
8449d9b81f7Schristos static int
md_pre_An(struct roff_node * n)8459d9b81f7Schristos md_pre_An(struct roff_node *n)
8469d9b81f7Schristos {
8479d9b81f7Schristos 	switch (n->norm->An.auth) {
8489d9b81f7Schristos 	case AUTH_split:
8499d9b81f7Schristos 		outflags &= ~MD_An_nosplit;
8509d9b81f7Schristos 		outflags |= MD_An_split;
8519d9b81f7Schristos 		return 0;
8529d9b81f7Schristos 	case AUTH_nosplit:
8539d9b81f7Schristos 		outflags &= ~MD_An_split;
8549d9b81f7Schristos 		outflags |= MD_An_nosplit;
8559d9b81f7Schristos 		return 0;
8569d9b81f7Schristos 	default:
8579d9b81f7Schristos 		if (outflags & MD_An_split)
8589d9b81f7Schristos 			outflags |= MD_br;
8599d9b81f7Schristos 		else if (n->sec == SEC_AUTHORS &&
8609d9b81f7Schristos 		    ! (outflags & MD_An_nosplit))
8619d9b81f7Schristos 			outflags |= MD_An_split;
8629d9b81f7Schristos 		return 1;
8639d9b81f7Schristos 	}
8649d9b81f7Schristos }
8659d9b81f7Schristos 
8669d9b81f7Schristos static int
md_pre_Ap(struct roff_node * n)8679d9b81f7Schristos md_pre_Ap(struct roff_node *n)
8689d9b81f7Schristos {
8699d9b81f7Schristos 	outflags &= ~MD_spc;
8709d9b81f7Schristos 	md_word("'");
8719d9b81f7Schristos 	outflags &= ~MD_spc;
8729d9b81f7Schristos 	return 0;
8739d9b81f7Schristos }
8749d9b81f7Schristos 
8759d9b81f7Schristos static int
md_pre_Bd(struct roff_node * n)8769d9b81f7Schristos md_pre_Bd(struct roff_node *n)
8779d9b81f7Schristos {
8789d9b81f7Schristos 	switch (n->norm->Bd.type) {
8799d9b81f7Schristos 	case DISP_unfilled:
8809d9b81f7Schristos 	case DISP_literal:
8819d9b81f7Schristos 		return md_pre_Dl(n);
8829d9b81f7Schristos 	default:
8839d9b81f7Schristos 		return md_pre_D1(n);
8849d9b81f7Schristos 	}
8859d9b81f7Schristos }
8869d9b81f7Schristos 
8879d9b81f7Schristos static int
md_pre_Bk(struct roff_node * n)8889d9b81f7Schristos md_pre_Bk(struct roff_node *n)
8899d9b81f7Schristos {
8909d9b81f7Schristos 	switch (n->type) {
8919d9b81f7Schristos 	case ROFFT_BLOCK:
8929d9b81f7Schristos 		return 1;
8939d9b81f7Schristos 	case ROFFT_BODY:
8949d9b81f7Schristos 		outflags |= MD_Bk;
8959d9b81f7Schristos 		return 1;
8969d9b81f7Schristos 	default:
8979d9b81f7Schristos 		return 0;
8989d9b81f7Schristos 	}
8999d9b81f7Schristos }
9009d9b81f7Schristos 
9019d9b81f7Schristos static void
md_post_Bk(struct roff_node * n)9029d9b81f7Schristos md_post_Bk(struct roff_node *n)
9039d9b81f7Schristos {
9049d9b81f7Schristos 	if (n->type == ROFFT_BODY)
9059d9b81f7Schristos 		outflags &= ~MD_Bk;
9069d9b81f7Schristos }
9079d9b81f7Schristos 
9089d9b81f7Schristos static int
md_pre_Bl(struct roff_node * n)9099d9b81f7Schristos md_pre_Bl(struct roff_node *n)
9109d9b81f7Schristos {
9119d9b81f7Schristos 	n->norm->Bl.count = 0;
9129d9b81f7Schristos 	if (n->norm->Bl.type == LIST_column)
9139d9b81f7Schristos 		md_pre_Dl(n);
9149d9b81f7Schristos 	outflags |= MD_sp;
9159d9b81f7Schristos 	return 1;
9169d9b81f7Schristos }
9179d9b81f7Schristos 
9189d9b81f7Schristos static void
md_post_Bl(struct roff_node * n)9199d9b81f7Schristos md_post_Bl(struct roff_node *n)
9209d9b81f7Schristos {
9219d9b81f7Schristos 	n->norm->Bl.count = 0;
9229d9b81f7Schristos 	if (n->norm->Bl.type == LIST_column)
9239d9b81f7Schristos 		md_post_D1(n);
9249d9b81f7Schristos 	outflags |= MD_sp;
9259d9b81f7Schristos }
9269d9b81f7Schristos 
9279d9b81f7Schristos static int
md_pre_D1(struct roff_node * n)9289d9b81f7Schristos md_pre_D1(struct roff_node *n)
9299d9b81f7Schristos {
9309d9b81f7Schristos 	/*
9319d9b81f7Schristos 	 * Markdown blockquote syntax does not work inside code blocks.
9329d9b81f7Schristos 	 * The best we can do is fall back to another nested code block.
9339d9b81f7Schristos 	 */
9349d9b81f7Schristos 	if (code_blocks) {
9359d9b81f7Schristos 		md_stack('\t');
9369d9b81f7Schristos 		code_blocks++;
9379d9b81f7Schristos 	} else {
9389d9b81f7Schristos 		md_stack('>');
9399d9b81f7Schristos 		quote_blocks++;
9409d9b81f7Schristos 	}
9419d9b81f7Schristos 	outflags |= MD_sp;
9429d9b81f7Schristos 	return 1;
9439d9b81f7Schristos }
9449d9b81f7Schristos 
9459d9b81f7Schristos static void
md_post_D1(struct roff_node * n)9469d9b81f7Schristos md_post_D1(struct roff_node *n)
9479d9b81f7Schristos {
9489d9b81f7Schristos 	md_stack((char)-1);
9499d9b81f7Schristos 	if (code_blocks)
9509d9b81f7Schristos 		code_blocks--;
9519d9b81f7Schristos 	else
9529d9b81f7Schristos 		quote_blocks--;
9539d9b81f7Schristos 	outflags |= MD_sp;
9549d9b81f7Schristos }
9559d9b81f7Schristos 
9569d9b81f7Schristos static int
md_pre_Dl(struct roff_node * n)9579d9b81f7Schristos md_pre_Dl(struct roff_node *n)
9589d9b81f7Schristos {
9599d9b81f7Schristos 	/*
9609d9b81f7Schristos 	 * Markdown code block syntax does not work inside blockquotes.
9619d9b81f7Schristos 	 * The best we can do is fall back to another nested blockquote.
9629d9b81f7Schristos 	 */
9639d9b81f7Schristos 	if (quote_blocks) {
9649d9b81f7Schristos 		md_stack('>');
9659d9b81f7Schristos 		quote_blocks++;
9669d9b81f7Schristos 	} else {
9679d9b81f7Schristos 		md_stack('\t');
9689d9b81f7Schristos 		code_blocks++;
9699d9b81f7Schristos 	}
9709d9b81f7Schristos 	outflags |= MD_sp;
9719d9b81f7Schristos 	return 1;
9729d9b81f7Schristos }
9739d9b81f7Schristos 
9749d9b81f7Schristos static int
md_pre_En(struct roff_node * n)9759d9b81f7Schristos md_pre_En(struct roff_node *n)
9769d9b81f7Schristos {
9779d9b81f7Schristos 	if (n->norm->Es == NULL ||
9789d9b81f7Schristos 	    n->norm->Es->child == NULL)
9799d9b81f7Schristos 		return 1;
9809d9b81f7Schristos 
9819d9b81f7Schristos 	md_word(n->norm->Es->child->string);
9829d9b81f7Schristos 	outflags &= ~MD_spc;
9839d9b81f7Schristos 	return 1;
9849d9b81f7Schristos }
9859d9b81f7Schristos 
9869d9b81f7Schristos static void
md_post_En(struct roff_node * n)9879d9b81f7Schristos md_post_En(struct roff_node *n)
9889d9b81f7Schristos {
9899d9b81f7Schristos 	if (n->norm->Es == NULL ||
9909d9b81f7Schristos 	    n->norm->Es->child == NULL ||
9919d9b81f7Schristos 	    n->norm->Es->child->next == NULL)
9929d9b81f7Schristos 		return;
9939d9b81f7Schristos 
9949d9b81f7Schristos 	outflags &= ~MD_spc;
9959d9b81f7Schristos 	md_word(n->norm->Es->child->next->string);
9969d9b81f7Schristos }
9979d9b81f7Schristos 
9989d9b81f7Schristos static int
md_pre_Eo(struct roff_node * n)9999d9b81f7Schristos md_pre_Eo(struct roff_node *n)
10009d9b81f7Schristos {
10019d9b81f7Schristos 	if (n->end == ENDBODY_NOT &&
10029d9b81f7Schristos 	    n->parent->head->child == NULL &&
10039d9b81f7Schristos 	    n->child != NULL &&
10049d9b81f7Schristos 	    n->child->end != ENDBODY_NOT)
10059d9b81f7Schristos 		md_preword();
10069d9b81f7Schristos 	else if (n->end != ENDBODY_NOT ? n->child != NULL :
10079d9b81f7Schristos 	    n->parent->head->child != NULL && (n->child != NULL ||
10089d9b81f7Schristos 	    (n->parent->tail != NULL && n->parent->tail->child != NULL)))
10099d9b81f7Schristos 		outflags &= ~(MD_spc | MD_nl);
10109d9b81f7Schristos 	return 1;
10119d9b81f7Schristos }
10129d9b81f7Schristos 
10139d9b81f7Schristos static void
md_post_Eo(struct roff_node * n)10149d9b81f7Schristos md_post_Eo(struct roff_node *n)
10159d9b81f7Schristos {
10169d9b81f7Schristos 	if (n->end != ENDBODY_NOT) {
10179d9b81f7Schristos 		outflags |= MD_spc;
10189d9b81f7Schristos 		return;
10199d9b81f7Schristos 	}
10209d9b81f7Schristos 
10219d9b81f7Schristos 	if (n->child == NULL && n->parent->head->child == NULL)
10229d9b81f7Schristos 		return;
10239d9b81f7Schristos 
10249d9b81f7Schristos 	if (n->parent->tail != NULL && n->parent->tail->child != NULL)
10259d9b81f7Schristos 		outflags &= ~MD_spc;
10269d9b81f7Schristos         else
10279d9b81f7Schristos 		outflags |= MD_spc;
10289d9b81f7Schristos }
10299d9b81f7Schristos 
10309d9b81f7Schristos static int
md_pre_Fa(struct roff_node * n)10319d9b81f7Schristos md_pre_Fa(struct roff_node *n)
10329d9b81f7Schristos {
10339d9b81f7Schristos 	int	 am_Fa;
10349d9b81f7Schristos 
10359d9b81f7Schristos 	am_Fa = n->tok == MDOC_Fa;
10369d9b81f7Schristos 
10379d9b81f7Schristos 	if (am_Fa)
10389d9b81f7Schristos 		n = n->child;
10399d9b81f7Schristos 
10409d9b81f7Schristos 	while (n != NULL) {
10419d9b81f7Schristos 		md_rawword("*");
10429d9b81f7Schristos 		outflags &= ~MD_spc;
10439d9b81f7Schristos 		md_node(n);
10449d9b81f7Schristos 		outflags &= ~MD_spc;
10459d9b81f7Schristos 		md_rawword("*");
10469d9b81f7Schristos 		if ((n = n->next) != NULL)
10479d9b81f7Schristos 			md_word(",");
10489d9b81f7Schristos 	}
10499d9b81f7Schristos 	return 0;
10509d9b81f7Schristos }
10519d9b81f7Schristos 
10529d9b81f7Schristos static void
md_post_Fa(struct roff_node * n)10539d9b81f7Schristos md_post_Fa(struct roff_node *n)
10549d9b81f7Schristos {
10559d9b81f7Schristos 	if (n->next != NULL && n->next->tok == MDOC_Fa)
10569d9b81f7Schristos 		md_word(",");
10579d9b81f7Schristos }
10589d9b81f7Schristos 
10599d9b81f7Schristos static int
md_pre_Fd(struct roff_node * n)10609d9b81f7Schristos md_pre_Fd(struct roff_node *n)
10619d9b81f7Schristos {
10629d9b81f7Schristos 	md_pre_syn(n);
10639d9b81f7Schristos 	md_pre_raw(n);
10649d9b81f7Schristos 	return 1;
10659d9b81f7Schristos }
10669d9b81f7Schristos 
10679d9b81f7Schristos static void
md_post_Fd(struct roff_node * n)10689d9b81f7Schristos md_post_Fd(struct roff_node *n)
10699d9b81f7Schristos {
10709d9b81f7Schristos 	md_post_raw(n);
10719d9b81f7Schristos 	outflags |= MD_br;
10729d9b81f7Schristos }
10739d9b81f7Schristos 
10749d9b81f7Schristos static void
md_post_Fl(struct roff_node * n)10759d9b81f7Schristos md_post_Fl(struct roff_node *n)
10769d9b81f7Schristos {
10779d9b81f7Schristos 	md_post_raw(n);
10789d9b81f7Schristos 	if (n->child == NULL && n->next != NULL &&
10799d9b81f7Schristos 	    n->next->type != ROFFT_TEXT && !(n->next->flags & NODE_LINE))
10809d9b81f7Schristos 		outflags &= ~MD_spc;
10819d9b81f7Schristos }
10829d9b81f7Schristos 
10839d9b81f7Schristos static int
md_pre_Fn(struct roff_node * n)10849d9b81f7Schristos md_pre_Fn(struct roff_node *n)
10859d9b81f7Schristos {
10869d9b81f7Schristos 	md_pre_syn(n);
10879d9b81f7Schristos 
10889d9b81f7Schristos 	if ((n = n->child) == NULL)
10899d9b81f7Schristos 		return 0;
10909d9b81f7Schristos 
10919d9b81f7Schristos 	md_rawword("**");
10929d9b81f7Schristos 	outflags &= ~MD_spc;
10939d9b81f7Schristos 	md_node(n);
10949d9b81f7Schristos 	outflags &= ~MD_spc;
10959d9b81f7Schristos 	md_rawword("**");
10969d9b81f7Schristos 	outflags &= ~MD_spc;
10979d9b81f7Schristos 	md_word("(");
10989d9b81f7Schristos 
10999d9b81f7Schristos 	if ((n = n->next) != NULL)
11009d9b81f7Schristos 		md_pre_Fa(n);
11019d9b81f7Schristos 	return 0;
11029d9b81f7Schristos }
11039d9b81f7Schristos 
11049d9b81f7Schristos static void
md_post_Fn(struct roff_node * n)11059d9b81f7Schristos md_post_Fn(struct roff_node *n)
11069d9b81f7Schristos {
11079d9b81f7Schristos 	md_word(")");
11089d9b81f7Schristos 	if (n->flags & NODE_SYNPRETTY) {
11099d9b81f7Schristos 		md_word(";");
11109d9b81f7Schristos 		outflags |= MD_sp;
11119d9b81f7Schristos 	}
11129d9b81f7Schristos }
11139d9b81f7Schristos 
11149d9b81f7Schristos static int
md_pre_Fo(struct roff_node * n)11159d9b81f7Schristos md_pre_Fo(struct roff_node *n)
11169d9b81f7Schristos {
11179d9b81f7Schristos 	switch (n->type) {
11189d9b81f7Schristos 	case ROFFT_BLOCK:
11199d9b81f7Schristos 		md_pre_syn(n);
11209d9b81f7Schristos 		break;
11219d9b81f7Schristos 	case ROFFT_HEAD:
11229d9b81f7Schristos 		if (n->child == NULL)
11239d9b81f7Schristos 			return 0;
11249d9b81f7Schristos 		md_pre_raw(n);
11259d9b81f7Schristos 		break;
11269d9b81f7Schristos 	case ROFFT_BODY:
11279d9b81f7Schristos 		outflags &= ~(MD_spc | MD_nl);
11289d9b81f7Schristos 		md_word("(");
11299d9b81f7Schristos 		break;
11309d9b81f7Schristos 	default:
11319d9b81f7Schristos 		break;
11329d9b81f7Schristos 	}
11339d9b81f7Schristos 	return 1;
11349d9b81f7Schristos }
11359d9b81f7Schristos 
11369d9b81f7Schristos static void
md_post_Fo(struct roff_node * n)11379d9b81f7Schristos md_post_Fo(struct roff_node *n)
11389d9b81f7Schristos {
11399d9b81f7Schristos 	switch (n->type) {
11409d9b81f7Schristos 	case ROFFT_HEAD:
11419d9b81f7Schristos 		if (n->child != NULL)
11429d9b81f7Schristos 			md_post_raw(n);
11439d9b81f7Schristos 		break;
11449d9b81f7Schristos 	case ROFFT_BODY:
11459d9b81f7Schristos 		md_post_Fn(n);
11469d9b81f7Schristos 		break;
11479d9b81f7Schristos 	default:
11489d9b81f7Schristos 		break;
11499d9b81f7Schristos 	}
11509d9b81f7Schristos }
11519d9b81f7Schristos 
11529d9b81f7Schristos static int
md_pre_In(struct roff_node * n)11539d9b81f7Schristos md_pre_In(struct roff_node *n)
11549d9b81f7Schristos {
11559d9b81f7Schristos 	if (n->flags & NODE_SYNPRETTY) {
11569d9b81f7Schristos 		md_pre_syn(n);
11579d9b81f7Schristos 		md_rawword("**");
11589d9b81f7Schristos 		outflags &= ~MD_spc;
11599d9b81f7Schristos 		md_word("#include <");
11609d9b81f7Schristos 	} else {
11619d9b81f7Schristos 		md_word("<");
11629d9b81f7Schristos 		outflags &= ~MD_spc;
11639d9b81f7Schristos 		md_rawword("*");
11649d9b81f7Schristos 	}
11659d9b81f7Schristos 	outflags &= ~MD_spc;
11669d9b81f7Schristos 	return 1;
11679d9b81f7Schristos }
11689d9b81f7Schristos 
11699d9b81f7Schristos static void
md_post_In(struct roff_node * n)11709d9b81f7Schristos md_post_In(struct roff_node *n)
11719d9b81f7Schristos {
11729d9b81f7Schristos 	if (n->flags & NODE_SYNPRETTY) {
11739d9b81f7Schristos 		outflags &= ~MD_spc;
11749d9b81f7Schristos 		md_rawword(">**");
11759d9b81f7Schristos 		outflags |= MD_nl;
11769d9b81f7Schristos 	} else {
11779d9b81f7Schristos 		outflags &= ~MD_spc;
11789d9b81f7Schristos 		md_rawword("*>");
11799d9b81f7Schristos 	}
11809d9b81f7Schristos }
11819d9b81f7Schristos 
11829d9b81f7Schristos static int
md_pre_It(struct roff_node * n)11839d9b81f7Schristos md_pre_It(struct roff_node *n)
11849d9b81f7Schristos {
11859d9b81f7Schristos 	struct roff_node	*bln;
11869d9b81f7Schristos 
11879d9b81f7Schristos 	switch (n->type) {
11889d9b81f7Schristos 	case ROFFT_BLOCK:
11899d9b81f7Schristos 		return 1;
11909d9b81f7Schristos 
11919d9b81f7Schristos 	case ROFFT_HEAD:
11929d9b81f7Schristos 		bln = n->parent->parent;
11939d9b81f7Schristos 		if (bln->norm->Bl.comp == 0 &&
11949d9b81f7Schristos 		    bln->norm->Bl.type != LIST_column)
11959d9b81f7Schristos 			outflags |= MD_sp;
11969d9b81f7Schristos 		outflags |= MD_nl;
11979d9b81f7Schristos 
11989d9b81f7Schristos 		switch (bln->norm->Bl.type) {
11999d9b81f7Schristos 		case LIST_item:
12009d9b81f7Schristos 			outflags |= MD_br;
12019d9b81f7Schristos 			return 0;
12029d9b81f7Schristos 		case LIST_inset:
12039d9b81f7Schristos 		case LIST_diag:
12049d9b81f7Schristos 		case LIST_ohang:
12059d9b81f7Schristos 			outflags |= MD_br;
12069d9b81f7Schristos 			return 1;
12079d9b81f7Schristos 		case LIST_tag:
12089d9b81f7Schristos 		case LIST_hang:
12099d9b81f7Schristos 			outflags |= MD_sp;
12109d9b81f7Schristos 			return 1;
12119d9b81f7Schristos 		case LIST_bullet:
12129d9b81f7Schristos 			md_rawword("*\t");
12139d9b81f7Schristos 			break;
12149d9b81f7Schristos 		case LIST_dash:
12159d9b81f7Schristos 		case LIST_hyphen:
12169d9b81f7Schristos 			md_rawword("-\t");
12179d9b81f7Schristos 			break;
12189d9b81f7Schristos 		case LIST_enum:
12199d9b81f7Schristos 			md_preword();
12209d9b81f7Schristos 			if (bln->norm->Bl.count < 99)
12219d9b81f7Schristos 				bln->norm->Bl.count++;
12229d9b81f7Schristos 			printf("%d.\t", bln->norm->Bl.count);
12239d9b81f7Schristos 			escflags &= ~ESC_FON;
12249d9b81f7Schristos 			break;
12259d9b81f7Schristos 		case LIST_column:
12269d9b81f7Schristos 			outflags |= MD_br;
12279d9b81f7Schristos 			return 0;
12289d9b81f7Schristos 		default:
12299d9b81f7Schristos 			return 0;
12309d9b81f7Schristos 		}
12319d9b81f7Schristos 		outflags &= ~MD_spc;
12329d9b81f7Schristos 		outflags |= MD_nonl;
12339d9b81f7Schristos 		outcount = 0;
12349d9b81f7Schristos 		md_stack('\t');
12359d9b81f7Schristos 		if (code_blocks || quote_blocks)
12369d9b81f7Schristos 			list_blocks++;
12379d9b81f7Schristos 		return 0;
12389d9b81f7Schristos 
12399d9b81f7Schristos 	case ROFFT_BODY:
12409d9b81f7Schristos 		bln = n->parent->parent;
12419d9b81f7Schristos 		switch (bln->norm->Bl.type) {
12429d9b81f7Schristos 		case LIST_ohang:
12439d9b81f7Schristos 			outflags |= MD_br;
12449d9b81f7Schristos 			break;
12459d9b81f7Schristos 		case LIST_tag:
12469d9b81f7Schristos 		case LIST_hang:
12479d9b81f7Schristos 			md_pre_D1(n);
12489d9b81f7Schristos 			break;
12499d9b81f7Schristos 		default:
12509d9b81f7Schristos 			break;
12519d9b81f7Schristos 		}
12529d9b81f7Schristos 		return 1;
12539d9b81f7Schristos 
12549d9b81f7Schristos 	default:
12559d9b81f7Schristos 		return 0;
12569d9b81f7Schristos 	}
12579d9b81f7Schristos }
12589d9b81f7Schristos 
12599d9b81f7Schristos static void
md_post_It(struct roff_node * n)12609d9b81f7Schristos md_post_It(struct roff_node *n)
12619d9b81f7Schristos {
12629d9b81f7Schristos 	struct roff_node	*bln;
12639d9b81f7Schristos 	int			 i, nc;
12649d9b81f7Schristos 
12659d9b81f7Schristos 	if (n->type != ROFFT_BODY)
12669d9b81f7Schristos 		return;
12679d9b81f7Schristos 
12689d9b81f7Schristos 	bln = n->parent->parent;
12699d9b81f7Schristos 	switch (bln->norm->Bl.type) {
12709d9b81f7Schristos 	case LIST_bullet:
12719d9b81f7Schristos 	case LIST_dash:
12729d9b81f7Schristos 	case LIST_hyphen:
12739d9b81f7Schristos 	case LIST_enum:
12749d9b81f7Schristos 		md_stack((char)-1);
12759d9b81f7Schristos 		if (code_blocks || quote_blocks)
12769d9b81f7Schristos 			list_blocks--;
12779d9b81f7Schristos 		break;
12789d9b81f7Schristos 	case LIST_tag:
12799d9b81f7Schristos 	case LIST_hang:
12809d9b81f7Schristos 		md_post_D1(n);
12819d9b81f7Schristos 		break;
12829d9b81f7Schristos 
12839d9b81f7Schristos 	case LIST_column:
12849d9b81f7Schristos 		if (n->next == NULL)
12859d9b81f7Schristos 			break;
12869d9b81f7Schristos 
12879d9b81f7Schristos 		/* Calculate the array index of the current column. */
12889d9b81f7Schristos 
12899d9b81f7Schristos 		i = 0;
12909d9b81f7Schristos 		while ((n = n->prev) != NULL && n->type != ROFFT_HEAD)
12919d9b81f7Schristos 			i++;
12929d9b81f7Schristos 
12939d9b81f7Schristos 		/*
12949d9b81f7Schristos 		 * If a width was specified for this column,
12959d9b81f7Schristos 		 * subtract what printed, and
12969d9b81f7Schristos 		 * add the same spacing as in mdoc_term.c.
12979d9b81f7Schristos 		 */
12989d9b81f7Schristos 
12999d9b81f7Schristos 		nc = bln->norm->Bl.ncols;
13009d9b81f7Schristos 		i = i < nc ? strlen(bln->norm->Bl.cols[i]) - outcount +
13019d9b81f7Schristos 		    (nc < 5 ? 4 : nc == 5 ? 3 : 1) : 1;
13029d9b81f7Schristos 		if (i < 1)
13039d9b81f7Schristos 			i = 1;
13049d9b81f7Schristos 		while (i-- > 0)
13059d9b81f7Schristos 			putchar(' ');
13069d9b81f7Schristos 
13079d9b81f7Schristos 		outflags &= ~MD_spc;
13089d9b81f7Schristos 		escflags &= ~ESC_FON;
13099d9b81f7Schristos 		outcount = 0;
13109d9b81f7Schristos 		break;
13119d9b81f7Schristos 
13129d9b81f7Schristos 	default:
13139d9b81f7Schristos 		break;
13149d9b81f7Schristos 	}
13159d9b81f7Schristos }
13169d9b81f7Schristos 
13179d9b81f7Schristos static void
md_post_Lb(struct roff_node * n)13189d9b81f7Schristos md_post_Lb(struct roff_node *n)
13199d9b81f7Schristos {
13209d9b81f7Schristos 	if (n->sec == SEC_LIBRARY)
13219d9b81f7Schristos 		outflags |= MD_br;
13229d9b81f7Schristos }
13239d9b81f7Schristos 
13249d9b81f7Schristos static void
md_uri(const char * s)13259d9b81f7Schristos md_uri(const char *s)
13269d9b81f7Schristos {
13279d9b81f7Schristos 	while (*s != '\0') {
13289d9b81f7Schristos 		if (strchr("%()<>", *s) != NULL) {
13299d9b81f7Schristos 			printf("%%%2.2hhX", *s);
13309d9b81f7Schristos 			outcount += 3;
13319d9b81f7Schristos 		} else {
13329d9b81f7Schristos 			putchar(*s);
13339d9b81f7Schristos 			outcount++;
13349d9b81f7Schristos 		}
13359d9b81f7Schristos 		s++;
13369d9b81f7Schristos 	}
13379d9b81f7Schristos }
13389d9b81f7Schristos 
13399d9b81f7Schristos static int
md_pre_Lk(struct roff_node * n)13409d9b81f7Schristos md_pre_Lk(struct roff_node *n)
13419d9b81f7Schristos {
13429d9b81f7Schristos 	const struct roff_node *link, *descr, *punct;
13439d9b81f7Schristos 
13449d9b81f7Schristos 	if ((link = n->child) == NULL)
13459d9b81f7Schristos 		return 0;
13469d9b81f7Schristos 
13479d9b81f7Schristos 	/* Find beginning of trailing punctuation. */
13489d9b81f7Schristos 	punct = n->last;
13499d9b81f7Schristos 	while (punct != link && punct->flags & NODE_DELIMC)
13509d9b81f7Schristos 		punct = punct->prev;
13519d9b81f7Schristos 	punct = punct->next;
13529d9b81f7Schristos 
13539d9b81f7Schristos 	/* Link text. */
13549d9b81f7Schristos 	descr = link->next;
13559d9b81f7Schristos 	if (descr == punct)
13569d9b81f7Schristos 		descr = link;  /* no text */
13579d9b81f7Schristos 	md_rawword("[");
13589d9b81f7Schristos 	outflags &= ~MD_spc;
13599d9b81f7Schristos 	do {
13609d9b81f7Schristos 		md_word(descr->string);
13619d9b81f7Schristos 		descr = descr->next;
13629d9b81f7Schristos 	} while (descr != punct);
13639d9b81f7Schristos 	outflags &= ~MD_spc;
13649d9b81f7Schristos 
13659d9b81f7Schristos 	/* Link target. */
13669d9b81f7Schristos 	md_rawword("](");
13679d9b81f7Schristos 	md_uri(link->string);
13689d9b81f7Schristos 	outflags &= ~MD_spc;
13699d9b81f7Schristos 	md_rawword(")");
13709d9b81f7Schristos 
13719d9b81f7Schristos 	/* Trailing punctuation. */
13729d9b81f7Schristos 	while (punct != NULL) {
13739d9b81f7Schristos 		md_word(punct->string);
13749d9b81f7Schristos 		punct = punct->next;
13759d9b81f7Schristos 	}
13769d9b81f7Schristos 	return 0;
13779d9b81f7Schristos }
13789d9b81f7Schristos 
13799d9b81f7Schristos static int
md_pre_Mt(struct roff_node * n)13809d9b81f7Schristos md_pre_Mt(struct roff_node *n)
13819d9b81f7Schristos {
13829d9b81f7Schristos 	const struct roff_node *nch;
13839d9b81f7Schristos 
13849d9b81f7Schristos 	md_rawword("[");
13859d9b81f7Schristos 	outflags &= ~MD_spc;
13869d9b81f7Schristos 	for (nch = n->child; nch != NULL; nch = nch->next)
13879d9b81f7Schristos 		md_word(nch->string);
13889d9b81f7Schristos 	outflags &= ~MD_spc;
13899d9b81f7Schristos 	md_rawword("](mailto:");
13909d9b81f7Schristos 	for (nch = n->child; nch != NULL; nch = nch->next) {
13919d9b81f7Schristos 		md_uri(nch->string);
13929d9b81f7Schristos 		if (nch->next != NULL) {
13939d9b81f7Schristos 			putchar(' ');
13949d9b81f7Schristos 			outcount++;
13959d9b81f7Schristos 		}
13969d9b81f7Schristos 	}
13979d9b81f7Schristos 	outflags &= ~MD_spc;
13989d9b81f7Schristos 	md_rawword(")");
13999d9b81f7Schristos 	return 0;
14009d9b81f7Schristos }
14019d9b81f7Schristos 
14029d9b81f7Schristos static int
md_pre_Nd(struct roff_node * n)14039d9b81f7Schristos md_pre_Nd(struct roff_node *n)
14049d9b81f7Schristos {
14059d9b81f7Schristos 	outflags &= ~MD_nl;
14069d9b81f7Schristos 	outflags |= MD_spc;
14079d9b81f7Schristos 	md_word("-");
14089d9b81f7Schristos 	return 1;
14099d9b81f7Schristos }
14109d9b81f7Schristos 
14119d9b81f7Schristos static int
md_pre_Nm(struct roff_node * n)14129d9b81f7Schristos md_pre_Nm(struct roff_node *n)
14139d9b81f7Schristos {
14149d9b81f7Schristos 	switch (n->type) {
14159d9b81f7Schristos 	case ROFFT_BLOCK:
14169d9b81f7Schristos 		outflags |= MD_Bk;
14179d9b81f7Schristos 		md_pre_syn(n);
14189d9b81f7Schristos 		break;
14199d9b81f7Schristos 	case ROFFT_HEAD:
14209d9b81f7Schristos 	case ROFFT_ELEM:
14219d9b81f7Schristos 		md_pre_raw(n);
14229d9b81f7Schristos 		break;
14239d9b81f7Schristos 	default:
14249d9b81f7Schristos 		break;
14259d9b81f7Schristos 	}
14269d9b81f7Schristos 	return 1;
14279d9b81f7Schristos }
14289d9b81f7Schristos 
14299d9b81f7Schristos static void
md_post_Nm(struct roff_node * n)14309d9b81f7Schristos md_post_Nm(struct roff_node *n)
14319d9b81f7Schristos {
14329d9b81f7Schristos 	switch (n->type) {
14339d9b81f7Schristos 	case ROFFT_BLOCK:
14349d9b81f7Schristos 		outflags &= ~MD_Bk;
14359d9b81f7Schristos 		break;
14369d9b81f7Schristos 	case ROFFT_HEAD:
14379d9b81f7Schristos 	case ROFFT_ELEM:
14389d9b81f7Schristos 		md_post_raw(n);
14399d9b81f7Schristos 		break;
14409d9b81f7Schristos 	default:
14419d9b81f7Schristos 		break;
14429d9b81f7Schristos 	}
14439d9b81f7Schristos }
14449d9b81f7Schristos 
14459d9b81f7Schristos static int
md_pre_No(struct roff_node * n)14469d9b81f7Schristos md_pre_No(struct roff_node *n)
14479d9b81f7Schristos {
14489d9b81f7Schristos 	outflags |= MD_spc_force;
14499d9b81f7Schristos 	return 1;
14509d9b81f7Schristos }
14519d9b81f7Schristos 
14529d9b81f7Schristos static int
md_pre_Ns(struct roff_node * n)14539d9b81f7Schristos md_pre_Ns(struct roff_node *n)
14549d9b81f7Schristos {
14559d9b81f7Schristos 	outflags &= ~MD_spc;
14569d9b81f7Schristos 	return 0;
14579d9b81f7Schristos }
14589d9b81f7Schristos 
14599d9b81f7Schristos static void
md_post_Pf(struct roff_node * n)14609d9b81f7Schristos md_post_Pf(struct roff_node *n)
14619d9b81f7Schristos {
14629d9b81f7Schristos 	if (n->next != NULL && (n->next->flags & NODE_LINE) == 0)
14639d9b81f7Schristos 		outflags &= ~MD_spc;
14649d9b81f7Schristos }
14659d9b81f7Schristos 
14669d9b81f7Schristos static int
md_pre_Pp(struct roff_node * n)14679d9b81f7Schristos md_pre_Pp(struct roff_node *n)
14689d9b81f7Schristos {
14699d9b81f7Schristos 	outflags |= MD_sp;
14709d9b81f7Schristos 	return 0;
14719d9b81f7Schristos }
14729d9b81f7Schristos 
14739d9b81f7Schristos static int
md_pre_Rs(struct roff_node * n)14749d9b81f7Schristos md_pre_Rs(struct roff_node *n)
14759d9b81f7Schristos {
14769d9b81f7Schristos 	if (n->sec == SEC_SEE_ALSO)
14779d9b81f7Schristos 		outflags |= MD_sp;
14789d9b81f7Schristos 	return 1;
14799d9b81f7Schristos }
14809d9b81f7Schristos 
14819d9b81f7Schristos static int
md_pre_Sh(struct roff_node * n)14829d9b81f7Schristos md_pre_Sh(struct roff_node *n)
14839d9b81f7Schristos {
14849d9b81f7Schristos 	switch (n->type) {
14859d9b81f7Schristos 	case ROFFT_BLOCK:
14869d9b81f7Schristos 		if (n->sec == SEC_AUTHORS)
14879d9b81f7Schristos 			outflags &= ~(MD_An_split | MD_An_nosplit);
14889d9b81f7Schristos 		break;
14899d9b81f7Schristos 	case ROFFT_HEAD:
14909d9b81f7Schristos 		outflags |= MD_sp;
14919d9b81f7Schristos 		md_rawword(n->tok == MDOC_Sh ? "#" : "##");
14929d9b81f7Schristos 		break;
14939d9b81f7Schristos 	case ROFFT_BODY:
14949d9b81f7Schristos 		outflags |= MD_sp;
14959d9b81f7Schristos 		break;
14969d9b81f7Schristos 	default:
14979d9b81f7Schristos 		break;
14989d9b81f7Schristos 	}
14999d9b81f7Schristos 	return 1;
15009d9b81f7Schristos }
15019d9b81f7Schristos 
15029d9b81f7Schristos static int
md_pre_Sm(struct roff_node * n)15039d9b81f7Schristos md_pre_Sm(struct roff_node *n)
15049d9b81f7Schristos {
15059d9b81f7Schristos 	if (n->child == NULL)
15069d9b81f7Schristos 		outflags ^= MD_Sm;
15079d9b81f7Schristos 	else if (strcmp("on", n->child->string) == 0)
15089d9b81f7Schristos 		outflags |= MD_Sm;
15099d9b81f7Schristos 	else
15109d9b81f7Schristos 		outflags &= ~MD_Sm;
15119d9b81f7Schristos 
15129d9b81f7Schristos 	if (outflags & MD_Sm)
15139d9b81f7Schristos 		outflags |= MD_spc;
15149d9b81f7Schristos 
15159d9b81f7Schristos 	return 0;
15169d9b81f7Schristos }
15179d9b81f7Schristos 
15189d9b81f7Schristos static int
md_pre_Vt(struct roff_node * n)15199d9b81f7Schristos md_pre_Vt(struct roff_node *n)
15209d9b81f7Schristos {
15219d9b81f7Schristos 	switch (n->type) {
15229d9b81f7Schristos 	case ROFFT_BLOCK:
15239d9b81f7Schristos 		md_pre_syn(n);
15249d9b81f7Schristos 		return 1;
15259d9b81f7Schristos 	case ROFFT_BODY:
15269d9b81f7Schristos 	case ROFFT_ELEM:
15279d9b81f7Schristos 		md_pre_raw(n);
15289d9b81f7Schristos 		return 1;
15299d9b81f7Schristos 	default:
15309d9b81f7Schristos 		return 0;
15319d9b81f7Schristos 	}
15329d9b81f7Schristos }
15339d9b81f7Schristos 
15349d9b81f7Schristos static void
md_post_Vt(struct roff_node * n)15359d9b81f7Schristos md_post_Vt(struct roff_node *n)
15369d9b81f7Schristos {
15379d9b81f7Schristos 	switch (n->type) {
15389d9b81f7Schristos 	case ROFFT_BODY:
15399d9b81f7Schristos 	case ROFFT_ELEM:
15409d9b81f7Schristos 		md_post_raw(n);
15419d9b81f7Schristos 		break;
15429d9b81f7Schristos 	default:
15439d9b81f7Schristos 		break;
15449d9b81f7Schristos 	}
15459d9b81f7Schristos }
15469d9b81f7Schristos 
15479d9b81f7Schristos static int
md_pre_Xr(struct roff_node * n)15489d9b81f7Schristos md_pre_Xr(struct roff_node *n)
15499d9b81f7Schristos {
15509d9b81f7Schristos 	n = n->child;
15519d9b81f7Schristos 	if (n == NULL)
15529d9b81f7Schristos 		return 0;
15539d9b81f7Schristos 	md_node(n);
15549d9b81f7Schristos 	n = n->next;
15559d9b81f7Schristos 	if (n == NULL)
15569d9b81f7Schristos 		return 0;
15579d9b81f7Schristos 	outflags &= ~MD_spc;
15589d9b81f7Schristos 	md_word("(");
15599d9b81f7Schristos 	md_node(n);
15609d9b81f7Schristos 	md_word(")");
15619d9b81f7Schristos 	return 0;
15629d9b81f7Schristos }
15639d9b81f7Schristos 
15649d9b81f7Schristos static int
md_pre__T(struct roff_node * n)15659d9b81f7Schristos md_pre__T(struct roff_node *n)
15669d9b81f7Schristos {
15679d9b81f7Schristos 	if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
15689d9b81f7Schristos 		md_word("\"");
15699d9b81f7Schristos 	else
15709d9b81f7Schristos 		md_rawword("*");
15719d9b81f7Schristos 	outflags &= ~MD_spc;
15729d9b81f7Schristos 	return 1;
15739d9b81f7Schristos }
15749d9b81f7Schristos 
15759d9b81f7Schristos static void
md_post__T(struct roff_node * n)15769d9b81f7Schristos md_post__T(struct roff_node *n)
15779d9b81f7Schristos {
15789d9b81f7Schristos 	outflags &= ~MD_spc;
15799d9b81f7Schristos 	if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
15809d9b81f7Schristos 		md_word("\"");
15819d9b81f7Schristos 	else
15829d9b81f7Schristos 		md_rawword("*");
15839d9b81f7Schristos 	md_post_pc(n);
15849d9b81f7Schristos }
15859d9b81f7Schristos 
15869d9b81f7Schristos static int
md_pre_br(struct roff_node * n)15879d9b81f7Schristos md_pre_br(struct roff_node *n)
15889d9b81f7Schristos {
15899d9b81f7Schristos 	outflags |= MD_br;
15909d9b81f7Schristos 	return 0;
15919d9b81f7Schristos }
1592