xref: /openbsd-src/usr.bin/mandoc/mdoc_markdown.c (revision bcf2ba68e7f719c8916fd19199756d4d37ff466d)
1*bcf2ba68Sschwarze /* $OpenBSD: mdoc_markdown.c,v 1.38 2025/01/20 00:52:00 schwarze Exp $ */
2b3257404Sschwarze /*
3*bcf2ba68Sschwarze  * Copyright (c) 2017, 2018, 2020, 2025 Ingo Schwarze <schwarze@openbsd.org>
4b3257404Sschwarze  *
5b3257404Sschwarze  * Permission to use, copy, modify, and distribute this software for any
6b3257404Sschwarze  * purpose with or without fee is hereby granted, provided that the above
7b3257404Sschwarze  * copyright notice and this permission notice appear in all copies.
8b3257404Sschwarze  *
9b3257404Sschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10b3257404Sschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11b3257404Sschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12b3257404Sschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13b3257404Sschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14b3257404Sschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15b3257404Sschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
166a6803e4Sschwarze  *
176a6803e4Sschwarze  * Markdown formatter for mdoc(7) used by mandoc(1).
18b3257404Sschwarze  */
19b3257404Sschwarze #include <sys/types.h>
20b3257404Sschwarze 
21b3257404Sschwarze #include <assert.h>
22b3257404Sschwarze #include <ctype.h>
23b3257404Sschwarze #include <stdio.h>
247c539ecbSschwarze #include <stdlib.h>
25b3257404Sschwarze #include <string.h>
26b3257404Sschwarze 
27b3257404Sschwarze #include "mandoc_aux.h"
28b3257404Sschwarze #include "mandoc.h"
29b3257404Sschwarze #include "roff.h"
30b3257404Sschwarze #include "mdoc.h"
31b3257404Sschwarze #include "main.h"
32b3257404Sschwarze 
33b3257404Sschwarze struct	md_act {
346a6803e4Sschwarze 	int		(*cond)(struct roff_node *);
356a6803e4Sschwarze 	int		(*pre)(struct roff_node *);
366a6803e4Sschwarze 	void		(*post)(struct roff_node *);
37b3257404Sschwarze 	const char	 *prefix; /* pre-node string constant */
38b3257404Sschwarze 	const char	 *suffix; /* post-node string constant */
39b3257404Sschwarze };
40b3257404Sschwarze 
41b3257404Sschwarze static	void	 md_nodelist(struct roff_node *);
42b3257404Sschwarze static	void	 md_node(struct roff_node *);
436a6803e4Sschwarze static	const char *md_stack(char);
44b3257404Sschwarze static	void	 md_preword(void);
45b3257404Sschwarze static	void	 md_rawword(const char *);
46b3257404Sschwarze static	void	 md_word(const char *);
47b3257404Sschwarze static	void	 md_named(const char *);
48b3257404Sschwarze static	void	 md_char(unsigned char);
4975d8c96aSschwarze static	void	 md_uri(const char *);
50b3257404Sschwarze 
51b3257404Sschwarze static	int	 md_cond_head(struct roff_node *);
52b3257404Sschwarze static	int	 md_cond_body(struct roff_node *);
53b3257404Sschwarze 
547c539ecbSschwarze static	int	 md_pre_abort(struct roff_node *);
55b3257404Sschwarze static	int	 md_pre_raw(struct roff_node *);
56b3257404Sschwarze static	int	 md_pre_word(struct roff_node *);
57b3257404Sschwarze static	int	 md_pre_skip(struct roff_node *);
58b3257404Sschwarze static	void	 md_pre_syn(struct roff_node *);
592fe9aa39Sschwarze static	int	 md_pre_An(struct roff_node *);
60b3257404Sschwarze static	int	 md_pre_Ap(struct roff_node *);
61b3257404Sschwarze static	int	 md_pre_Bd(struct roff_node *);
62b3257404Sschwarze static	int	 md_pre_Bk(struct roff_node *);
63b3257404Sschwarze static	int	 md_pre_Bl(struct roff_node *);
64b3257404Sschwarze static	int	 md_pre_D1(struct roff_node *);
65b3257404Sschwarze static	int	 md_pre_Dl(struct roff_node *);
66b3257404Sschwarze static	int	 md_pre_En(struct roff_node *);
67b3257404Sschwarze static	int	 md_pre_Eo(struct roff_node *);
68b3257404Sschwarze static	int	 md_pre_Fa(struct roff_node *);
69b3257404Sschwarze static	int	 md_pre_Fd(struct roff_node *);
70b3257404Sschwarze static	int	 md_pre_Fn(struct roff_node *);
71b3257404Sschwarze static	int	 md_pre_Fo(struct roff_node *);
72b3257404Sschwarze static	int	 md_pre_In(struct roff_node *);
73b3257404Sschwarze static	int	 md_pre_It(struct roff_node *);
74b3257404Sschwarze static	int	 md_pre_Lk(struct roff_node *);
7575d8c96aSschwarze static	int	 md_pre_Mt(struct roff_node *);
76b3257404Sschwarze static	int	 md_pre_Nd(struct roff_node *);
77b3257404Sschwarze static	int	 md_pre_Nm(struct roff_node *);
78b3257404Sschwarze static	int	 md_pre_No(struct roff_node *);
79b3257404Sschwarze static	int	 md_pre_Ns(struct roff_node *);
80b3257404Sschwarze static	int	 md_pre_Pp(struct roff_node *);
81b3257404Sschwarze static	int	 md_pre_Rs(struct roff_node *);
82b3257404Sschwarze static	int	 md_pre_Sh(struct roff_node *);
83b3257404Sschwarze static	int	 md_pre_Sm(struct roff_node *);
84b3257404Sschwarze static	int	 md_pre_Vt(struct roff_node *);
85b3257404Sschwarze static	int	 md_pre_Xr(struct roff_node *);
86*bcf2ba68Sschwarze static	int	 md_pre__R(struct roff_node *);
87b3257404Sschwarze static	int	 md_pre__T(struct roff_node *);
88b3257404Sschwarze static	int	 md_pre_br(struct roff_node *);
89b3257404Sschwarze 
90b3257404Sschwarze static	void	 md_post_raw(struct roff_node *);
91b3257404Sschwarze static	void	 md_post_word(struct roff_node *);
92b3257404Sschwarze static	void	 md_post_pc(struct roff_node *);
93b3257404Sschwarze static	void	 md_post_Bk(struct roff_node *);
94b3257404Sschwarze static	void	 md_post_Bl(struct roff_node *);
95b3257404Sschwarze static	void	 md_post_D1(struct roff_node *);
96b3257404Sschwarze static	void	 md_post_En(struct roff_node *);
97b3257404Sschwarze static	void	 md_post_Eo(struct roff_node *);
98b3257404Sschwarze static	void	 md_post_Fa(struct roff_node *);
99b3257404Sschwarze static	void	 md_post_Fd(struct roff_node *);
1008e60e820Sschwarze static	void	 md_post_Fl(struct roff_node *);
101b3257404Sschwarze static	void	 md_post_Fn(struct roff_node *);
102b3257404Sschwarze static	void	 md_post_Fo(struct roff_node *);
103b3257404Sschwarze static	void	 md_post_In(struct roff_node *);
104b3257404Sschwarze static	void	 md_post_It(struct roff_node *);
105b3257404Sschwarze static	void	 md_post_Lb(struct roff_node *);
106b3257404Sschwarze static	void	 md_post_Nm(struct roff_node *);
107b3257404Sschwarze static	void	 md_post_Pf(struct roff_node *);
108b3257404Sschwarze static	void	 md_post_Vt(struct roff_node *);
109b3257404Sschwarze static	void	 md_post__T(struct roff_node *);
110b3257404Sschwarze 
11116fe0cfcSschwarze static	const struct md_act md_acts[MDOC_MAX - MDOC_Dd] = {
112b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Dd */
113b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Dt */
114b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Os */
115b3257404Sschwarze 	{ NULL, md_pre_Sh, NULL, NULL, NULL }, /* Sh */
116b3257404Sschwarze 	{ NULL, md_pre_Sh, NULL, NULL, NULL }, /* Ss */
117b3257404Sschwarze 	{ NULL, md_pre_Pp, NULL, NULL, NULL }, /* Pp */
118b3257404Sschwarze 	{ md_cond_body, md_pre_D1, md_post_D1, NULL, NULL }, /* D1 */
119b3257404Sschwarze 	{ md_cond_body, md_pre_Dl, md_post_D1, NULL, NULL }, /* Dl */
120b3257404Sschwarze 	{ md_cond_body, md_pre_Bd, md_post_D1, NULL, NULL }, /* Bd */
121b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Ed */
122b3257404Sschwarze 	{ md_cond_body, md_pre_Bl, md_post_Bl, NULL, NULL }, /* Bl */
123b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* El */
124b3257404Sschwarze 	{ NULL, md_pre_It, md_post_It, NULL, NULL }, /* It */
125b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ad */
1262fe9aa39Sschwarze 	{ NULL, md_pre_An, NULL, NULL, NULL }, /* An */
12714a309e3Sschwarze 	{ NULL, md_pre_Ap, NULL, NULL, NULL }, /* Ap */
128b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ar */
129b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cd */
130b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cm */
131b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Dv */
132b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Er */
133b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Ev */
134b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Ex */
135b3257404Sschwarze 	{ NULL, md_pre_Fa, md_post_Fa, NULL, NULL }, /* Fa */
136b3257404Sschwarze 	{ NULL, md_pre_Fd, md_post_Fd, "**", "**" }, /* Fd */
1378e60e820Sschwarze 	{ NULL, md_pre_raw, md_post_Fl, "**-", "**" }, /* Fl */
138b3257404Sschwarze 	{ NULL, md_pre_Fn, md_post_Fn, NULL, NULL }, /* Fn */
139b3257404Sschwarze 	{ NULL, md_pre_Fd, md_post_raw, "*", "*" }, /* Ft */
140b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ic */
1415f580400Sschwarze 	{ NULL, md_pre_In, md_post_In, NULL, NULL }, /* In */
142b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Li */
143b3257404Sschwarze 	{ md_cond_head, md_pre_Nd, NULL, NULL, NULL }, /* Nd */
144b3257404Sschwarze 	{ NULL, md_pre_Nm, md_post_Nm, "**", "**" }, /* Nm */
145b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Op */
1467c539ecbSschwarze 	{ NULL, md_pre_abort, NULL, NULL, NULL }, /* Ot */
147b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Pa */
148b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Rv */
149b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* St */
150b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Va */
151b3257404Sschwarze 	{ NULL, md_pre_Vt, md_post_Vt, "*", "*" }, /* Vt */
152b3257404Sschwarze 	{ NULL, md_pre_Xr, NULL, NULL, NULL }, /* Xr */
153b3257404Sschwarze 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %A */
154b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %B */
155b3257404Sschwarze 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %D */
156b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %I */
157b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %J */
158b3257404Sschwarze 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %N */
159b3257404Sschwarze 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %O */
160b3257404Sschwarze 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %P */
161*bcf2ba68Sschwarze 	{ NULL, md_pre__R, md_post_pc, NULL, NULL }, /* %R */
162b3257404Sschwarze 	{ NULL, md_pre__T, md_post__T, NULL, NULL }, /* %T */
163b3257404Sschwarze 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %V */
164b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Ac */
165b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Ao */
166b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Aq */
167b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* At */
168b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Bc */
169b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Bf XXX not implemented */
170b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bo */
171b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bq */
172b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Bsx */
173b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Bx */
174b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Db */
175b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Dc */
176b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Do */
177b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Dq */
178b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Ec */
179b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Ef */
180b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Em */
181b3257404Sschwarze 	{ md_cond_body, md_pre_Eo, md_post_Eo, NULL, NULL }, /* Eo */
182b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Fx */
183b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ms */
184b3257404Sschwarze 	{ NULL, md_pre_No, NULL, NULL, NULL }, /* No */
185b3257404Sschwarze 	{ NULL, md_pre_Ns, NULL, NULL, NULL }, /* Ns */
186b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Nx */
187b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Ox */
188b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Pc */
189b3257404Sschwarze 	{ NULL, NULL, md_post_Pf, NULL, NULL }, /* Pf */
190b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Po */
191b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Pq */
192b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Qc */
193b3257404Sschwarze 	{ md_cond_body, md_pre_raw, md_post_raw, "'`", "`'" }, /* Ql */
194b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qo */
195b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qq */
196b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Re */
197b3257404Sschwarze 	{ md_cond_body, md_pre_Rs, NULL, NULL, NULL }, /* Rs */
198b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Sc */
199b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* So */
200b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* Sq */
201b3257404Sschwarze 	{ NULL, md_pre_Sm, NULL, NULL, NULL }, /* Sm */
202b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Sx */
203b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Sy */
204b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Tn */
205b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Ux */
206b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Xc */
207b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Xo */
208b3257404Sschwarze 	{ NULL, md_pre_Fo, md_post_Fo, "**", "**" }, /* Fo */
209b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Fc */
210b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Oo */
211b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Oc */
212b3257404Sschwarze 	{ NULL, md_pre_Bk, md_post_Bk, NULL, NULL }, /* Bk */
213b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Ek */
214b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Bt */
215b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Hf */
216b3257404Sschwarze 	{ NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Fr */
217b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Ud */
218b3257404Sschwarze 	{ NULL, NULL, md_post_Lb, NULL, NULL }, /* Lb */
2197c539ecbSschwarze 	{ NULL, md_pre_abort, NULL, NULL, NULL }, /* Lp */
220b3257404Sschwarze 	{ NULL, md_pre_Lk, NULL, NULL, NULL }, /* Lk */
22175d8c96aSschwarze 	{ NULL, md_pre_Mt, NULL, NULL, NULL }, /* Mt */
222b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Brq */
223b3257404Sschwarze 	{ md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Bro */
224b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Brc */
225b3257404Sschwarze 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %C */
226b3257404Sschwarze 	{ NULL, md_pre_skip, NULL, NULL, NULL }, /* Es */
227b3257404Sschwarze 	{ md_cond_body, md_pre_En, md_post_En, NULL, NULL }, /* En */
228b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Dx */
229b3257404Sschwarze 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %Q */
23078ed643eSschwarze 	{ NULL, md_pre_Lk, md_post_pc, NULL, NULL }, /* %U */
231b3257404Sschwarze 	{ NULL, NULL, NULL, NULL, NULL }, /* Ta */
2328b1bef1aSschwarze 	{ NULL, md_pre_skip, NULL, NULL, NULL }, /* Tg */
233b3257404Sschwarze };
23416fe0cfcSschwarze static const struct md_act *md_act(enum roff_tok);
235b3257404Sschwarze 
236b3257404Sschwarze static	int	 outflags;
237b3257404Sschwarze #define	MD_spc		 (1 << 0)  /* Blank character before next word. */
238b3257404Sschwarze #define	MD_spc_force	 (1 << 1)  /* Even before trailing punctuation. */
239b3257404Sschwarze #define	MD_nonl		 (1 << 2)  /* Prevent linebreak in markdown code. */
240b3257404Sschwarze #define	MD_nl		 (1 << 3)  /* Break markdown code line. */
241b3257404Sschwarze #define	MD_br		 (1 << 4)  /* Insert an output line break. */
242b3257404Sschwarze #define	MD_sp		 (1 << 5)  /* Insert a paragraph break. */
243b3257404Sschwarze #define	MD_Sm		 (1 << 6)  /* Horizontal spacing mode. */
244b3257404Sschwarze #define	MD_Bk		 (1 << 7)  /* Word keep mode. */
2452fe9aa39Sschwarze #define	MD_An_split	 (1 << 8)  /* Author mode is "split". */
2462fe9aa39Sschwarze #define	MD_An_nosplit	 (1 << 9)  /* Author mode is "nosplit". */
247b3257404Sschwarze 
248b3257404Sschwarze static	int	 escflags; /* Escape in generated markdown code: */
249b3257404Sschwarze #define	ESC_BOL	 (1 << 0)  /* "#*+-" near the beginning of a line. */
250b3257404Sschwarze #define	ESC_NUM	 (1 << 1)  /* "." after a leading number. */
251b3257404Sschwarze #define	ESC_HYP	 (1 << 2)  /* "(" immediately after "]". */
252b3257404Sschwarze #define	ESC_SQU	 (1 << 4)  /* "]" when "[" is open. */
253b3257404Sschwarze #define	ESC_FON	 (1 << 5)  /* "*" immediately after unrelated "*". */
254bed87e42Sschwarze #define	ESC_EOL	 (1 << 6)  /* " " at the and of a line. */
255b3257404Sschwarze 
256b3257404Sschwarze static	int	 code_blocks, quote_blocks, list_blocks;
257b3257404Sschwarze static	int	 outcount;
258b3257404Sschwarze 
25916fe0cfcSschwarze 
26016fe0cfcSschwarze static const struct md_act *
26116fe0cfcSschwarze md_act(enum roff_tok tok)
26216fe0cfcSschwarze {
26316fe0cfcSschwarze 	assert(tok >= MDOC_Dd && tok <= MDOC_MAX);
26416fe0cfcSschwarze 	return md_acts + (tok - MDOC_Dd);
26516fe0cfcSschwarze }
26616fe0cfcSschwarze 
267b3257404Sschwarze void
2686b86842eSschwarze markdown_mdoc(void *arg, const struct roff_meta *mdoc)
269b3257404Sschwarze {
270b3257404Sschwarze 	outflags = MD_Sm;
2716b86842eSschwarze 	md_word(mdoc->title);
2726b86842eSschwarze 	if (mdoc->msec != NULL) {
273b3257404Sschwarze 		outflags &= ~MD_spc;
274b3257404Sschwarze 		md_word("(");
2756b86842eSschwarze 		md_word(mdoc->msec);
276b3257404Sschwarze 		md_word(")");
277b3257404Sschwarze 	}
278b3257404Sschwarze 	md_word("-");
2796b86842eSschwarze 	md_word(mdoc->vol);
2806b86842eSschwarze 	if (mdoc->arch != NULL) {
281b3257404Sschwarze 		md_word("(");
2826b86842eSschwarze 		md_word(mdoc->arch);
283b3257404Sschwarze 		md_word(")");
284b3257404Sschwarze 	}
285b3257404Sschwarze 	outflags |= MD_sp;
286b3257404Sschwarze 
287b3257404Sschwarze 	md_nodelist(mdoc->first->child);
288b3257404Sschwarze 
289b3257404Sschwarze 	outflags |= MD_sp;
2906b86842eSschwarze 	md_word(mdoc->os);
291b3257404Sschwarze 	md_word("-");
2926b86842eSschwarze 	md_word(mdoc->date);
293b3257404Sschwarze 	putchar('\n');
294b3257404Sschwarze }
295b3257404Sschwarze 
296b3257404Sschwarze static void
297b3257404Sschwarze md_nodelist(struct roff_node *n)
298b3257404Sschwarze {
299b3257404Sschwarze 	while (n != NULL) {
300b3257404Sschwarze 		md_node(n);
301b3257404Sschwarze 		n = n->next;
302b3257404Sschwarze 	}
303b3257404Sschwarze }
304b3257404Sschwarze 
305b3257404Sschwarze static void
306b3257404Sschwarze md_node(struct roff_node *n)
307b3257404Sschwarze {
308b3257404Sschwarze 	const struct md_act	*act;
309b3257404Sschwarze 	int			 cond, process_children;
310b3257404Sschwarze 
3114c293873Sschwarze 	if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
312b3257404Sschwarze 		return;
313b3257404Sschwarze 
314b3257404Sschwarze 	if (outflags & MD_nonl)
315b3257404Sschwarze 		outflags &= ~(MD_nl | MD_sp);
3167ebbefbeSschwarze 	else if (outflags & MD_spc &&
3177ebbefbeSschwarze 	     n->flags & NODE_LINE &&
3187ebbefbeSschwarze 	     !roff_node_transparent(n))
319b3257404Sschwarze 		outflags |= MD_nl;
320b3257404Sschwarze 
321b3257404Sschwarze 	act = NULL;
322b3257404Sschwarze 	cond = 0;
323b3257404Sschwarze 	process_children = 1;
324b3257404Sschwarze 	n->flags &= ~NODE_ENDED;
325b3257404Sschwarze 
32629478532Sschwarze 	if (n->type == ROFFT_TEXT) {
327b3257404Sschwarze 		if (n->flags & NODE_DELIMC)
328b3257404Sschwarze 			outflags &= ~(MD_spc | MD_spc_force);
329b3257404Sschwarze 		else if (outflags & MD_Sm)
330b3257404Sschwarze 			outflags |= MD_spc_force;
331b3257404Sschwarze 		md_word(n->string);
332b3257404Sschwarze 		if (n->flags & NODE_DELIMO)
333b3257404Sschwarze 			outflags &= ~(MD_spc | MD_spc_force);
334b3257404Sschwarze 		else if (outflags & MD_Sm)
335b3257404Sschwarze 			outflags |= MD_spc;
33629478532Sschwarze 	} else if (n->tok < ROFF_MAX) {
33729478532Sschwarze 		switch (n->tok) {
33829478532Sschwarze 		case ROFF_br:
339c4d3fa85Sschwarze 			process_children = md_pre_br(n);
340c4d3fa85Sschwarze 			break;
3416561cb23Sschwarze 		case ROFF_sp:
3426561cb23Sschwarze 			process_children = md_pre_Pp(n);
3436561cb23Sschwarze 			break;
344644b390bSschwarze 		default:
345c4d3fa85Sschwarze 			process_children = 0;
346b3257404Sschwarze 			break;
34729478532Sschwarze 		}
34829478532Sschwarze 	} else {
34916fe0cfcSschwarze 		act = md_act(n->tok);
350b3257404Sschwarze 		cond = act->cond == NULL || (*act->cond)(n);
351b3257404Sschwarze 		if (cond && act->pre != NULL &&
352b3257404Sschwarze 		    (n->end == ENDBODY_NOT || n->child != NULL))
353b3257404Sschwarze 			process_children = (*act->pre)(n);
354b3257404Sschwarze 	}
355b3257404Sschwarze 
356b3257404Sschwarze 	if (process_children && n->child != NULL)
357b3257404Sschwarze 		md_nodelist(n->child);
358b3257404Sschwarze 
359b3257404Sschwarze 	if (n->flags & NODE_ENDED)
360b3257404Sschwarze 		return;
361b3257404Sschwarze 
362b3257404Sschwarze 	if (cond && act->post != NULL)
363b3257404Sschwarze 		(*act->post)(n);
364b3257404Sschwarze 
365b3257404Sschwarze 	if (n->end != ENDBODY_NOT)
366b3257404Sschwarze 		n->body->flags |= NODE_ENDED;
367b3257404Sschwarze }
368b3257404Sschwarze 
369b3257404Sschwarze static const char *
370b3257404Sschwarze md_stack(char c)
371b3257404Sschwarze {
372b3257404Sschwarze 	static char	*stack;
373b3257404Sschwarze 	static size_t	 sz;
374b3257404Sschwarze 	static size_t	 cur;
375b3257404Sschwarze 
376b3257404Sschwarze 	switch (c) {
377b3257404Sschwarze 	case '\0':
378b3257404Sschwarze 		break;
379b3257404Sschwarze 	case (char)-1:
380b3257404Sschwarze 		assert(cur);
381b3257404Sschwarze 		stack[--cur] = '\0';
382b3257404Sschwarze 		break;
383b3257404Sschwarze 	default:
384b3257404Sschwarze 		if (cur + 1 >= sz) {
385b3257404Sschwarze 			sz += 8;
386b3257404Sschwarze 			stack = mandoc_realloc(stack, sz);
387b3257404Sschwarze 		}
388b3257404Sschwarze 		stack[cur] = c;
389b3257404Sschwarze 		stack[++cur] = '\0';
390b3257404Sschwarze 		break;
391b3257404Sschwarze 	}
392b3257404Sschwarze 	return stack == NULL ? "" : stack;
393b3257404Sschwarze }
394b3257404Sschwarze 
395b3257404Sschwarze /*
396b3257404Sschwarze  * Handle vertical and horizontal spacing.
397b3257404Sschwarze  */
398b3257404Sschwarze static void
399b3257404Sschwarze md_preword(void)
400b3257404Sschwarze {
4010b477a38Sschwarze 	const char	*cp;
4020b477a38Sschwarze 
403b3257404Sschwarze 	/*
404b3257404Sschwarze 	 * If a list block is nested inside a code block or a blockquote,
405b3257404Sschwarze 	 * blank lines for paragraph breaks no longer work; instead,
406b3257404Sschwarze 	 * they terminate the list.  Work around this markdown issue
407b3257404Sschwarze 	 * by using mere line breaks instead.
408b3257404Sschwarze 	 */
409bed87e42Sschwarze 
410b3257404Sschwarze 	if (list_blocks && outflags & MD_sp) {
411b3257404Sschwarze 		outflags &= ~MD_sp;
412b3257404Sschwarze 		outflags |= MD_br;
413b3257404Sschwarze 	}
414b3257404Sschwarze 
415bed87e42Sschwarze 	/*
416bed87e42Sschwarze 	 * End the old line if requested.
417bed87e42Sschwarze 	 * Escape whitespace at the end of the markdown line
418bed87e42Sschwarze 	 * such that it won't look like an output line break.
419bed87e42Sschwarze 	 */
420b3257404Sschwarze 
421b3257404Sschwarze 	if (outflags & MD_sp)
422b3257404Sschwarze 		putchar('\n');
423b3257404Sschwarze 	else if (outflags & MD_br) {
424b3257404Sschwarze 		putchar(' ');
425b3257404Sschwarze 		putchar(' ');
426bed87e42Sschwarze 	} else if (outflags & MD_nl && escflags & ESC_EOL)
427bed87e42Sschwarze 		md_named("zwnj");
428b3257404Sschwarze 
429b3257404Sschwarze 	/* Start a new line if necessary. */
430b3257404Sschwarze 
431b3257404Sschwarze 	if (outflags & (MD_nl | MD_br | MD_sp)) {
432b3257404Sschwarze 		putchar('\n');
4330b477a38Sschwarze 		for (cp = md_stack('\0'); *cp != '\0'; cp++) {
4340b477a38Sschwarze 			putchar(*cp);
4350b477a38Sschwarze 			if (*cp == '>')
4360b477a38Sschwarze 				putchar(' ');
4370b477a38Sschwarze 		}
438b3257404Sschwarze 		outflags &= ~(MD_nl | MD_br | MD_sp);
439b3257404Sschwarze 		escflags = ESC_BOL;
440b3257404Sschwarze 		outcount = 0;
441b3257404Sschwarze 
442b3257404Sschwarze 	/* Handle horizontal spacing. */
443b3257404Sschwarze 
444b3257404Sschwarze 	} else if (outflags & MD_spc) {
445b3257404Sschwarze 		if (outflags & MD_Bk)
446b3257404Sschwarze 			fputs("&nbsp;", stdout);
447b3257404Sschwarze 		else
448b3257404Sschwarze 			putchar(' ');
449b3257404Sschwarze 		escflags &= ~ESC_FON;
450b3257404Sschwarze 		outcount++;
451b3257404Sschwarze 	}
452b3257404Sschwarze 
453b3257404Sschwarze 	outflags &= ~(MD_spc_force | MD_nonl);
454b3257404Sschwarze 	if (outflags & MD_Sm)
455b3257404Sschwarze 		outflags |= MD_spc;
456b3257404Sschwarze 	else
457b3257404Sschwarze 		outflags &= ~MD_spc;
458b3257404Sschwarze }
459b3257404Sschwarze 
460b3257404Sschwarze /*
461b3257404Sschwarze  * Print markdown syntax elements.
462b3257404Sschwarze  * Can also be used for constant strings when neither escaping
463b3257404Sschwarze  * nor delimiter handling is required.
464b3257404Sschwarze  */
465b3257404Sschwarze static void
466b3257404Sschwarze md_rawword(const char *s)
467b3257404Sschwarze {
468b3257404Sschwarze 	md_preword();
469b3257404Sschwarze 
470bed87e42Sschwarze 	if (*s == '\0')
471b3257404Sschwarze 		return;
472b3257404Sschwarze 
473b3257404Sschwarze 	if (escflags & ESC_FON) {
474b3257404Sschwarze 		escflags &= ~ESC_FON;
475b3257404Sschwarze 		if (*s == '*' && !code_blocks)
476b3257404Sschwarze 			fputs("&zwnj;", stdout);
477b3257404Sschwarze 	}
478b3257404Sschwarze 
479b3257404Sschwarze 	while (*s != '\0') {
480b3257404Sschwarze 		switch(*s) {
481b3257404Sschwarze 		case '*':
482b3257404Sschwarze 			if (s[1] == '\0')
483b3257404Sschwarze 				escflags |= ESC_FON;
484b3257404Sschwarze 			break;
485b3257404Sschwarze 		case '[':
486b3257404Sschwarze 			escflags |= ESC_SQU;
487b3257404Sschwarze 			break;
488b3257404Sschwarze 		case ']':
489b3257404Sschwarze 			escflags |= ESC_HYP;
490b3257404Sschwarze 			escflags &= ~ESC_SQU;
491b3257404Sschwarze 			break;
492b3257404Sschwarze 		default:
493b3257404Sschwarze 			break;
494b3257404Sschwarze 		}
495b3257404Sschwarze 		md_char(*s++);
496b3257404Sschwarze 	}
497bed87e42Sschwarze 	if (s[-1] == ' ')
498bed87e42Sschwarze 		escflags |= ESC_EOL;
499bed87e42Sschwarze 	else
500bed87e42Sschwarze 		escflags &= ~ESC_EOL;
501b3257404Sschwarze }
502b3257404Sschwarze 
503b3257404Sschwarze /*
504b3257404Sschwarze  * Print text and mdoc(7) syntax elements.
505b3257404Sschwarze  */
506b3257404Sschwarze static void
507b3257404Sschwarze md_word(const char *s)
508b3257404Sschwarze {
509b3257404Sschwarze 	const char	*seq, *prevfont, *currfont, *nextfont;
510b3257404Sschwarze 	char		 c;
5116167ec38Sschwarze 	int		 bs, sz, uc, breakline;
512b3257404Sschwarze 
513b3257404Sschwarze 	/* No spacing before closing delimiters. */
514b3257404Sschwarze 	if (s[0] != '\0' && s[1] == '\0' &&
515b3257404Sschwarze 	    strchr("!),.:;?]", s[0]) != NULL &&
516b3257404Sschwarze 	    (outflags & MD_spc_force) == 0)
517b3257404Sschwarze 		outflags &= ~MD_spc;
518b3257404Sschwarze 
519b3257404Sschwarze 	md_preword();
520b3257404Sschwarze 
521bed87e42Sschwarze 	if (*s == '\0')
522bed87e42Sschwarze 		return;
523bed87e42Sschwarze 
524b3257404Sschwarze 	/* No spacing after opening delimiters. */
525b3257404Sschwarze 	if ((s[0] == '(' || s[0] == '[') && s[1] == '\0')
526b3257404Sschwarze 		outflags &= ~MD_spc;
527b3257404Sschwarze 
5286167ec38Sschwarze 	breakline = 0;
529b3257404Sschwarze 	prevfont = currfont = "";
530b3257404Sschwarze 	while ((c = *s++) != '\0') {
531b3257404Sschwarze 		bs = 0;
532b3257404Sschwarze 		switch(c) {
533b3257404Sschwarze 		case ASCII_NBRSP:
534b3257404Sschwarze 			if (code_blocks)
535b3257404Sschwarze 				c = ' ';
536b3257404Sschwarze 			else {
537b3257404Sschwarze 				md_named("nbsp");
538b3257404Sschwarze 				c = '\0';
539b3257404Sschwarze 			}
540b3257404Sschwarze 			break;
541b3257404Sschwarze 		case ASCII_HYPH:
542b3257404Sschwarze 			bs = escflags & ESC_BOL && !code_blocks;
543b3257404Sschwarze 			c = '-';
544b3257404Sschwarze 			break;
545b3257404Sschwarze 		case ASCII_BREAK:
546b3257404Sschwarze 			continue;
547b3257404Sschwarze 		case '#':
548b3257404Sschwarze 		case '+':
549b3257404Sschwarze 		case '-':
550b3257404Sschwarze 			bs = escflags & ESC_BOL && !code_blocks;
551b3257404Sschwarze 			break;
552b3257404Sschwarze 		case '(':
553b3257404Sschwarze 			bs = escflags & ESC_HYP && !code_blocks;
554b3257404Sschwarze 			break;
555b3257404Sschwarze 		case ')':
556433f0baeSschwarze 			bs = escflags & ESC_NUM && !code_blocks;
557b3257404Sschwarze 			break;
558b3257404Sschwarze 		case '*':
559b3257404Sschwarze 		case '[':
560b3257404Sschwarze 		case '_':
561b3257404Sschwarze 		case '`':
562b3257404Sschwarze 			bs = !code_blocks;
563b3257404Sschwarze 			break;
564b3257404Sschwarze 		case '.':
565b3257404Sschwarze 			bs = escflags & ESC_NUM && !code_blocks;
566b3257404Sschwarze 			break;
567b3257404Sschwarze 		case '<':
568b3257404Sschwarze 			if (code_blocks == 0) {
569b3257404Sschwarze 				md_named("lt");
570b3257404Sschwarze 				c = '\0';
571b3257404Sschwarze 			}
572b3257404Sschwarze 			break;
573b3257404Sschwarze 		case '=':
574b3257404Sschwarze 			if (escflags & ESC_BOL && !code_blocks) {
575b3257404Sschwarze 				md_named("equals");
576b3257404Sschwarze 				c = '\0';
577b3257404Sschwarze 			}
578b3257404Sschwarze 			break;
579b3257404Sschwarze 		case '>':
580b3257404Sschwarze 			if (code_blocks == 0) {
581b3257404Sschwarze 				md_named("gt");
582b3257404Sschwarze 				c = '\0';
583b3257404Sschwarze 			}
584b3257404Sschwarze 			break;
585b3257404Sschwarze 		case '\\':
586b3257404Sschwarze 			uc = 0;
587b3257404Sschwarze 			nextfont = NULL;
588b3257404Sschwarze 			switch (mandoc_escape(&s, &seq, &sz)) {
589b3257404Sschwarze 			case ESCAPE_UNICODE:
590b3257404Sschwarze 				uc = mchars_num2uc(seq + 1, sz - 1);
591b3257404Sschwarze 				break;
592b3257404Sschwarze 			case ESCAPE_NUMBERED:
593b3257404Sschwarze 				uc = mchars_num2char(seq, sz);
594b3257404Sschwarze 				break;
595b3257404Sschwarze 			case ESCAPE_SPECIAL:
596b3257404Sschwarze 				uc = mchars_spec2cp(seq, sz);
597b3257404Sschwarze 				break;
5986f6722cbSschwarze 			case ESCAPE_UNDEF:
5996f6722cbSschwarze 				uc = *seq;
6006f6722cbSschwarze 				break;
6018138dde8Sschwarze 			case ESCAPE_DEVICE:
6028138dde8Sschwarze 				md_rawword("markdown");
6038138dde8Sschwarze 				continue;
604b3257404Sschwarze 			case ESCAPE_FONTBOLD:
6057d063611Sschwarze 			case ESCAPE_FONTCB:
606b3257404Sschwarze 				nextfont = "**";
607b3257404Sschwarze 				break;
608b3257404Sschwarze 			case ESCAPE_FONTITALIC:
6097d063611Sschwarze 			case ESCAPE_FONTCI:
610b3257404Sschwarze 				nextfont = "*";
611b3257404Sschwarze 				break;
612b3257404Sschwarze 			case ESCAPE_FONTBI:
613b3257404Sschwarze 				nextfont = "***";
614b3257404Sschwarze 				break;
615b3257404Sschwarze 			case ESCAPE_FONT:
6167d063611Sschwarze 			case ESCAPE_FONTCR:
617b3257404Sschwarze 			case ESCAPE_FONTROMAN:
618b3257404Sschwarze 				nextfont = "";
619b3257404Sschwarze 				break;
620b3257404Sschwarze 			case ESCAPE_FONTPREV:
621b3257404Sschwarze 				nextfont = prevfont;
622b3257404Sschwarze 				break;
6236167ec38Sschwarze 			case ESCAPE_BREAK:
6246167ec38Sschwarze 				breakline = 1;
6256167ec38Sschwarze 				break;
626b3257404Sschwarze 			case ESCAPE_NOSPACE:
627b3257404Sschwarze 			case ESCAPE_SKIPCHAR:
628b3257404Sschwarze 			case ESCAPE_OVERSTRIKE:
629b3257404Sschwarze 				/* XXX not implemented */
630b3257404Sschwarze 				/* FALLTHROUGH */
631b3257404Sschwarze 			case ESCAPE_ERROR:
632b3257404Sschwarze 			default:
633b3257404Sschwarze 				break;
634b3257404Sschwarze 			}
635b3257404Sschwarze 			if (nextfont != NULL && !code_blocks) {
636b3257404Sschwarze 				if (*currfont != '\0') {
637b3257404Sschwarze 					outflags &= ~MD_spc;
638b3257404Sschwarze 					md_rawword(currfont);
639b3257404Sschwarze 				}
640b3257404Sschwarze 				prevfont = currfont;
641b3257404Sschwarze 				currfont = nextfont;
642b3257404Sschwarze 				if (*currfont != '\0') {
643b3257404Sschwarze 					outflags &= ~MD_spc;
644b3257404Sschwarze 					md_rawword(currfont);
645b3257404Sschwarze 				}
646b3257404Sschwarze 			}
647b3257404Sschwarze 			if (uc) {
648b3257404Sschwarze 				if ((uc < 0x20 && uc != 0x09) ||
649b3257404Sschwarze 				    (uc > 0x7E && uc < 0xA0))
650b3257404Sschwarze 					uc = 0xFFFD;
651b3257404Sschwarze 				if (code_blocks) {
652b3257404Sschwarze 					seq = mchars_uc2str(uc);
653b3257404Sschwarze 					fputs(seq, stdout);
654b3257404Sschwarze 					outcount += strlen(seq);
655b3257404Sschwarze 				} else {
656b3257404Sschwarze 					printf("&#%d;", uc);
657b3257404Sschwarze 					outcount++;
658b3257404Sschwarze 				}
659b3257404Sschwarze 				escflags &= ~ESC_FON;
660b3257404Sschwarze 			}
661b3257404Sschwarze 			c = '\0';
662b3257404Sschwarze 			break;
663b3257404Sschwarze 		case ']':
664b3257404Sschwarze 			bs = escflags & ESC_SQU && !code_blocks;
665b3257404Sschwarze 			escflags |= ESC_HYP;
666b3257404Sschwarze 			break;
667b3257404Sschwarze 		default:
668b3257404Sschwarze 			break;
669b3257404Sschwarze 		}
670b3257404Sschwarze 		if (bs)
671b3257404Sschwarze 			putchar('\\');
672b3257404Sschwarze 		md_char(c);
6736167ec38Sschwarze 		if (breakline &&
6746167ec38Sschwarze 		    (*s == '\0' || *s == ' ' || *s == ASCII_NBRSP)) {
6756167ec38Sschwarze 			printf("  \n");
6766167ec38Sschwarze 			breakline = 0;
6776167ec38Sschwarze 			while (*s == ' ' || *s == ASCII_NBRSP)
6786167ec38Sschwarze 				s++;
6796167ec38Sschwarze 		}
680b3257404Sschwarze 	}
681b3257404Sschwarze 	if (*currfont != '\0') {
682b3257404Sschwarze 		outflags &= ~MD_spc;
683b3257404Sschwarze 		md_rawword(currfont);
684bed87e42Sschwarze 	} else if (s[-2] == ' ')
685bed87e42Sschwarze 		escflags |= ESC_EOL;
686bed87e42Sschwarze 	else
687bed87e42Sschwarze 		escflags &= ~ESC_EOL;
688b3257404Sschwarze }
689b3257404Sschwarze 
690b3257404Sschwarze /*
691b3257404Sschwarze  * Print a single HTML named character reference.
692b3257404Sschwarze  */
693b3257404Sschwarze static void
694b3257404Sschwarze md_named(const char *s)
695b3257404Sschwarze {
696b3257404Sschwarze 	printf("&%s;", s);
697bed87e42Sschwarze 	escflags &= ~(ESC_FON | ESC_EOL);
698b3257404Sschwarze 	outcount++;
699b3257404Sschwarze }
700b3257404Sschwarze 
701b3257404Sschwarze /*
702b3257404Sschwarze  * Print a single raw character and maintain certain escape flags.
703b3257404Sschwarze  */
704b3257404Sschwarze static void
705b3257404Sschwarze md_char(unsigned char c)
706b3257404Sschwarze {
707b3257404Sschwarze 	if (c != '\0') {
708b3257404Sschwarze 		putchar(c);
709b3257404Sschwarze 		if (c == '*')
710b3257404Sschwarze 			escflags |= ESC_FON;
711b3257404Sschwarze 		else
712b3257404Sschwarze 			escflags &= ~ESC_FON;
713b3257404Sschwarze 		outcount++;
714b3257404Sschwarze 	}
715b3257404Sschwarze 	if (c != ']')
716b3257404Sschwarze 		escflags &= ~ESC_HYP;
717b3257404Sschwarze 	if (c == ' ' || c == '\t' || c == '>')
718b3257404Sschwarze 		return;
719b3257404Sschwarze 	if (isdigit(c) == 0)
720b3257404Sschwarze 		escflags &= ~ESC_NUM;
721b3257404Sschwarze 	else if (escflags & ESC_BOL)
722b3257404Sschwarze 		escflags |= ESC_NUM;
723b3257404Sschwarze 	escflags &= ~ESC_BOL;
724b3257404Sschwarze }
725b3257404Sschwarze 
726b3257404Sschwarze static int
727b3257404Sschwarze md_cond_head(struct roff_node *n)
728b3257404Sschwarze {
729b3257404Sschwarze 	return n->type == ROFFT_HEAD;
730b3257404Sschwarze }
731b3257404Sschwarze 
732b3257404Sschwarze static int
733b3257404Sschwarze md_cond_body(struct roff_node *n)
734b3257404Sschwarze {
735b3257404Sschwarze 	return n->type == ROFFT_BODY;
736b3257404Sschwarze }
737b3257404Sschwarze 
738b3257404Sschwarze static int
7397c539ecbSschwarze md_pre_abort(struct roff_node *n)
7407c539ecbSschwarze {
7417c539ecbSschwarze 	abort();
7427c539ecbSschwarze }
7437c539ecbSschwarze 
7447c539ecbSschwarze static int
745b3257404Sschwarze md_pre_raw(struct roff_node *n)
746b3257404Sschwarze {
747b3257404Sschwarze 	const char	*prefix;
748b3257404Sschwarze 
74916fe0cfcSschwarze 	if ((prefix = md_act(n->tok)->prefix) != NULL) {
750b3257404Sschwarze 		md_rawword(prefix);
751b3257404Sschwarze 		outflags &= ~MD_spc;
7520272cb5cSschwarze 		if (strchr(prefix, '`') != NULL)
753113c1f55Sschwarze 			code_blocks++;
754b3257404Sschwarze 	}
755b3257404Sschwarze 	return 1;
756b3257404Sschwarze }
757b3257404Sschwarze 
758b3257404Sschwarze static void
759b3257404Sschwarze md_post_raw(struct roff_node *n)
760b3257404Sschwarze {
761b3257404Sschwarze 	const char	*suffix;
762b3257404Sschwarze 
76316fe0cfcSschwarze 	if ((suffix = md_act(n->tok)->suffix) != NULL) {
764b3257404Sschwarze 		outflags &= ~(MD_spc | MD_nl);
765b3257404Sschwarze 		md_rawword(suffix);
7660272cb5cSschwarze 		if (strchr(suffix, '`') != NULL)
767113c1f55Sschwarze 			code_blocks--;
768b3257404Sschwarze 	}
769b3257404Sschwarze }
770b3257404Sschwarze 
771b3257404Sschwarze static int
772b3257404Sschwarze md_pre_word(struct roff_node *n)
773b3257404Sschwarze {
774b3257404Sschwarze 	const char	*prefix;
775b3257404Sschwarze 
77616fe0cfcSschwarze 	if ((prefix = md_act(n->tok)->prefix) != NULL) {
777b3257404Sschwarze 		md_word(prefix);
778b3257404Sschwarze 		outflags &= ~MD_spc;
779b3257404Sschwarze 	}
780b3257404Sschwarze 	return 1;
781b3257404Sschwarze }
782b3257404Sschwarze 
783b3257404Sschwarze static void
784b3257404Sschwarze md_post_word(struct roff_node *n)
785b3257404Sschwarze {
786b3257404Sschwarze 	const char	*suffix;
787b3257404Sschwarze 
78816fe0cfcSschwarze 	if ((suffix = md_act(n->tok)->suffix) != NULL) {
789b3257404Sschwarze 		outflags &= ~(MD_spc | MD_nl);
790b3257404Sschwarze 		md_word(suffix);
791b3257404Sschwarze 	}
792b3257404Sschwarze }
793b3257404Sschwarze 
794b3257404Sschwarze static void
795b3257404Sschwarze md_post_pc(struct roff_node *n)
796b3257404Sschwarze {
7977ebbefbeSschwarze 	struct roff_node *nn;
7987ebbefbeSschwarze 
799b3257404Sschwarze 	md_post_raw(n);
800b3257404Sschwarze 	if (n->parent->tok != MDOC_Rs)
801b3257404Sschwarze 		return;
8027ebbefbeSschwarze 
8037ebbefbeSschwarze 	if ((nn = roff_node_next(n)) != NULL) {
804b3257404Sschwarze 		md_word(",");
8057ebbefbeSschwarze 		if (nn->tok == n->tok &&
8067ebbefbeSschwarze 		    (nn = roff_node_prev(n)) != NULL &&
8077ebbefbeSschwarze 		    nn->tok == n->tok)
808b3257404Sschwarze 			md_word("and");
809b3257404Sschwarze 	} else {
810b3257404Sschwarze 		md_word(".");
811b3257404Sschwarze 		outflags |= MD_nl;
812b3257404Sschwarze 	}
813b3257404Sschwarze }
814b3257404Sschwarze 
815b3257404Sschwarze static int
816b3257404Sschwarze md_pre_skip(struct roff_node *n)
817b3257404Sschwarze {
818b3257404Sschwarze 	return 0;
819b3257404Sschwarze }
820b3257404Sschwarze 
821b3257404Sschwarze static void
822b3257404Sschwarze md_pre_syn(struct roff_node *n)
823b3257404Sschwarze {
8247ebbefbeSschwarze 	struct roff_node *np;
8257ebbefbeSschwarze 
8267ebbefbeSschwarze 	if ((n->flags & NODE_SYNPRETTY) == 0 ||
8277ebbefbeSschwarze 	    (np = roff_node_prev(n)) == NULL)
828b3257404Sschwarze 		return;
829b3257404Sschwarze 
8307ebbefbeSschwarze 	if (np->tok == n->tok &&
831b3257404Sschwarze 	    n->tok != MDOC_Ft &&
832b3257404Sschwarze 	    n->tok != MDOC_Fo &&
833b3257404Sschwarze 	    n->tok != MDOC_Fn) {
834b3257404Sschwarze 		outflags |= MD_br;
835b3257404Sschwarze 		return;
836b3257404Sschwarze 	}
837b3257404Sschwarze 
8387ebbefbeSschwarze 	switch (np->tok) {
839b3257404Sschwarze 	case MDOC_Fd:
840b3257404Sschwarze 	case MDOC_Fn:
841b3257404Sschwarze 	case MDOC_Fo:
842b3257404Sschwarze 	case MDOC_In:
843b3257404Sschwarze 	case MDOC_Vt:
844b3257404Sschwarze 		outflags |= MD_sp;
845b3257404Sschwarze 		break;
846b3257404Sschwarze 	case MDOC_Ft:
847b3257404Sschwarze 		if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) {
848b3257404Sschwarze 			outflags |= MD_sp;
849b3257404Sschwarze 			break;
850b3257404Sschwarze 		}
851b3257404Sschwarze 		/* FALLTHROUGH */
852b3257404Sschwarze 	default:
853b3257404Sschwarze 		outflags |= MD_br;
854b3257404Sschwarze 		break;
855b3257404Sschwarze 	}
856b3257404Sschwarze }
857b3257404Sschwarze 
858b3257404Sschwarze static int
8592fe9aa39Sschwarze md_pre_An(struct roff_node *n)
8602fe9aa39Sschwarze {
8612fe9aa39Sschwarze 	switch (n->norm->An.auth) {
8622fe9aa39Sschwarze 	case AUTH_split:
8632fe9aa39Sschwarze 		outflags &= ~MD_An_nosplit;
8642fe9aa39Sschwarze 		outflags |= MD_An_split;
8652fe9aa39Sschwarze 		return 0;
8662fe9aa39Sschwarze 	case AUTH_nosplit:
8672fe9aa39Sschwarze 		outflags &= ~MD_An_split;
8682fe9aa39Sschwarze 		outflags |= MD_An_nosplit;
8692fe9aa39Sschwarze 		return 0;
8702fe9aa39Sschwarze 	default:
8712fe9aa39Sschwarze 		if (outflags & MD_An_split)
8722fe9aa39Sschwarze 			outflags |= MD_br;
8732fe9aa39Sschwarze 		else if (n->sec == SEC_AUTHORS &&
8742fe9aa39Sschwarze 		    ! (outflags & MD_An_nosplit))
8752fe9aa39Sschwarze 			outflags |= MD_An_split;
8762fe9aa39Sschwarze 		return 1;
8772fe9aa39Sschwarze 	}
8782fe9aa39Sschwarze }
8792fe9aa39Sschwarze 
8802fe9aa39Sschwarze static int
881b3257404Sschwarze md_pre_Ap(struct roff_node *n)
882b3257404Sschwarze {
883b3257404Sschwarze 	outflags &= ~MD_spc;
884b3257404Sschwarze 	md_word("'");
885b3257404Sschwarze 	outflags &= ~MD_spc;
886b3257404Sschwarze 	return 0;
887b3257404Sschwarze }
888b3257404Sschwarze 
889b3257404Sschwarze static int
890b3257404Sschwarze md_pre_Bd(struct roff_node *n)
891b3257404Sschwarze {
892b3257404Sschwarze 	switch (n->norm->Bd.type) {
893b3257404Sschwarze 	case DISP_unfilled:
894b3257404Sschwarze 	case DISP_literal:
895b3257404Sschwarze 		return md_pre_Dl(n);
896b3257404Sschwarze 	default:
897b3257404Sschwarze 		return md_pre_D1(n);
898b3257404Sschwarze 	}
899b3257404Sschwarze }
900b3257404Sschwarze 
901b3257404Sschwarze static int
902b3257404Sschwarze md_pre_Bk(struct roff_node *n)
903b3257404Sschwarze {
904b3257404Sschwarze 	switch (n->type) {
905b3257404Sschwarze 	case ROFFT_BLOCK:
906b3257404Sschwarze 		return 1;
907b3257404Sschwarze 	case ROFFT_BODY:
908b3257404Sschwarze 		outflags |= MD_Bk;
909b3257404Sschwarze 		return 1;
910b3257404Sschwarze 	default:
911b3257404Sschwarze 		return 0;
912b3257404Sschwarze 	}
913b3257404Sschwarze }
914b3257404Sschwarze 
915b3257404Sschwarze static void
916b3257404Sschwarze md_post_Bk(struct roff_node *n)
917b3257404Sschwarze {
918b3257404Sschwarze 	if (n->type == ROFFT_BODY)
919b3257404Sschwarze 		outflags &= ~MD_Bk;
920b3257404Sschwarze }
921b3257404Sschwarze 
922b3257404Sschwarze static int
923b3257404Sschwarze md_pre_Bl(struct roff_node *n)
924b3257404Sschwarze {
925b3257404Sschwarze 	n->norm->Bl.count = 0;
926b3257404Sschwarze 	if (n->norm->Bl.type == LIST_column)
927b3257404Sschwarze 		md_pre_Dl(n);
928b3257404Sschwarze 	outflags |= MD_sp;
929b3257404Sschwarze 	return 1;
930b3257404Sschwarze }
931b3257404Sschwarze 
932b3257404Sschwarze static void
933b3257404Sschwarze md_post_Bl(struct roff_node *n)
934b3257404Sschwarze {
935b3257404Sschwarze 	n->norm->Bl.count = 0;
936b3257404Sschwarze 	if (n->norm->Bl.type == LIST_column)
937b3257404Sschwarze 		md_post_D1(n);
938b3257404Sschwarze 	outflags |= MD_sp;
939b3257404Sschwarze }
940b3257404Sschwarze 
941b3257404Sschwarze static int
942b3257404Sschwarze md_pre_D1(struct roff_node *n)
943b3257404Sschwarze {
944b3257404Sschwarze 	/*
945b3257404Sschwarze 	 * Markdown blockquote syntax does not work inside code blocks.
946b3257404Sschwarze 	 * The best we can do is fall back to another nested code block.
947b3257404Sschwarze 	 */
948b3257404Sschwarze 	if (code_blocks) {
949b3257404Sschwarze 		md_stack('\t');
950b3257404Sschwarze 		code_blocks++;
951b3257404Sschwarze 	} else {
952b3257404Sschwarze 		md_stack('>');
953b3257404Sschwarze 		quote_blocks++;
954b3257404Sschwarze 	}
955b3257404Sschwarze 	outflags |= MD_sp;
956b3257404Sschwarze 	return 1;
957b3257404Sschwarze }
958b3257404Sschwarze 
959b3257404Sschwarze static void
960b3257404Sschwarze md_post_D1(struct roff_node *n)
961b3257404Sschwarze {
962b3257404Sschwarze 	md_stack((char)-1);
963b3257404Sschwarze 	if (code_blocks)
964b3257404Sschwarze 		code_blocks--;
965b3257404Sschwarze 	else
966b3257404Sschwarze 		quote_blocks--;
967b3257404Sschwarze 	outflags |= MD_sp;
968b3257404Sschwarze }
969b3257404Sschwarze 
970b3257404Sschwarze static int
971b3257404Sschwarze md_pre_Dl(struct roff_node *n)
972b3257404Sschwarze {
973b3257404Sschwarze 	/*
974b3257404Sschwarze 	 * Markdown code block syntax does not work inside blockquotes.
975b3257404Sschwarze 	 * The best we can do is fall back to another nested blockquote.
976b3257404Sschwarze 	 */
977b3257404Sschwarze 	if (quote_blocks) {
978b3257404Sschwarze 		md_stack('>');
979b3257404Sschwarze 		quote_blocks++;
980b3257404Sschwarze 	} else {
981b3257404Sschwarze 		md_stack('\t');
982b3257404Sschwarze 		code_blocks++;
983b3257404Sschwarze 	}
984b3257404Sschwarze 	outflags |= MD_sp;
985b3257404Sschwarze 	return 1;
986b3257404Sschwarze }
987b3257404Sschwarze 
988b3257404Sschwarze static int
989b3257404Sschwarze md_pre_En(struct roff_node *n)
990b3257404Sschwarze {
991b3257404Sschwarze 	if (n->norm->Es == NULL ||
992b3257404Sschwarze 	    n->norm->Es->child == NULL)
993b3257404Sschwarze 		return 1;
994b3257404Sschwarze 
995b3257404Sschwarze 	md_word(n->norm->Es->child->string);
996b3257404Sschwarze 	outflags &= ~MD_spc;
997b3257404Sschwarze 	return 1;
998b3257404Sschwarze }
999b3257404Sschwarze 
1000b3257404Sschwarze static void
1001b3257404Sschwarze md_post_En(struct roff_node *n)
1002b3257404Sschwarze {
1003b3257404Sschwarze 	if (n->norm->Es == NULL ||
1004b3257404Sschwarze 	    n->norm->Es->child == NULL ||
1005b3257404Sschwarze 	    n->norm->Es->child->next == NULL)
1006b3257404Sschwarze 		return;
1007b3257404Sschwarze 
1008b3257404Sschwarze 	outflags &= ~MD_spc;
1009b3257404Sschwarze 	md_word(n->norm->Es->child->next->string);
1010b3257404Sschwarze }
1011b3257404Sschwarze 
1012b3257404Sschwarze static int
1013b3257404Sschwarze md_pre_Eo(struct roff_node *n)
1014b3257404Sschwarze {
1015b3257404Sschwarze 	if (n->end == ENDBODY_NOT &&
1016b3257404Sschwarze 	    n->parent->head->child == NULL &&
1017b3257404Sschwarze 	    n->child != NULL &&
1018b3257404Sschwarze 	    n->child->end != ENDBODY_NOT)
1019b3257404Sschwarze 		md_preword();
1020b3257404Sschwarze 	else if (n->end != ENDBODY_NOT ? n->child != NULL :
1021b3257404Sschwarze 	    n->parent->head->child != NULL && (n->child != NULL ||
1022b3257404Sschwarze 	    (n->parent->tail != NULL && n->parent->tail->child != NULL)))
1023b3257404Sschwarze 		outflags &= ~(MD_spc | MD_nl);
1024b3257404Sschwarze 	return 1;
1025b3257404Sschwarze }
1026b3257404Sschwarze 
1027b3257404Sschwarze static void
1028b3257404Sschwarze md_post_Eo(struct roff_node *n)
1029b3257404Sschwarze {
1030b3257404Sschwarze 	if (n->end != ENDBODY_NOT) {
1031b3257404Sschwarze 		outflags |= MD_spc;
1032b3257404Sschwarze 		return;
1033b3257404Sschwarze 	}
1034b3257404Sschwarze 
10359956bc5fSschwarze 	if (n->child == NULL && n->parent->head->child == NULL)
10369956bc5fSschwarze 		return;
1037b3257404Sschwarze 
10389956bc5fSschwarze 	if (n->parent->tail != NULL && n->parent->tail->child != NULL)
1039b3257404Sschwarze 		outflags &= ~MD_spc;
10409956bc5fSschwarze         else
1041b3257404Sschwarze 		outflags |= MD_spc;
1042b3257404Sschwarze }
1043b3257404Sschwarze 
1044b3257404Sschwarze static int
1045b3257404Sschwarze md_pre_Fa(struct roff_node *n)
1046b3257404Sschwarze {
1047b3257404Sschwarze 	int	 am_Fa;
1048b3257404Sschwarze 
1049b3257404Sschwarze 	am_Fa = n->tok == MDOC_Fa;
1050b3257404Sschwarze 
1051b3257404Sschwarze 	if (am_Fa)
1052b3257404Sschwarze 		n = n->child;
1053b3257404Sschwarze 
1054b3257404Sschwarze 	while (n != NULL) {
1055b3257404Sschwarze 		md_rawword("*");
1056b3257404Sschwarze 		outflags &= ~MD_spc;
1057b3257404Sschwarze 		md_node(n);
1058b3257404Sschwarze 		outflags &= ~MD_spc;
1059b3257404Sschwarze 		md_rawword("*");
1060b3257404Sschwarze 		if ((n = n->next) != NULL)
1061b3257404Sschwarze 			md_word(",");
1062b3257404Sschwarze 	}
1063b3257404Sschwarze 	return 0;
1064b3257404Sschwarze }
1065b3257404Sschwarze 
1066b3257404Sschwarze static void
1067b3257404Sschwarze md_post_Fa(struct roff_node *n)
1068b3257404Sschwarze {
10697ebbefbeSschwarze 	struct roff_node *nn;
10707ebbefbeSschwarze 
10717ebbefbeSschwarze 	if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa)
1072b3257404Sschwarze 		md_word(",");
1073b3257404Sschwarze }
1074b3257404Sschwarze 
1075b3257404Sschwarze static int
1076b3257404Sschwarze md_pre_Fd(struct roff_node *n)
1077b3257404Sschwarze {
1078b3257404Sschwarze 	md_pre_syn(n);
1079b3257404Sschwarze 	md_pre_raw(n);
1080b3257404Sschwarze 	return 1;
1081b3257404Sschwarze }
1082b3257404Sschwarze 
1083b3257404Sschwarze static void
1084b3257404Sschwarze md_post_Fd(struct roff_node *n)
1085b3257404Sschwarze {
1086b3257404Sschwarze 	md_post_raw(n);
1087b3257404Sschwarze 	outflags |= MD_br;
1088b3257404Sschwarze }
1089b3257404Sschwarze 
10908e60e820Sschwarze static void
10918e60e820Sschwarze md_post_Fl(struct roff_node *n)
10928e60e820Sschwarze {
10937ebbefbeSschwarze 	struct roff_node *nn;
10947ebbefbeSschwarze 
10958e60e820Sschwarze 	md_post_raw(n);
10967ebbefbeSschwarze 	if (n->child == NULL && (nn = roff_node_next(n)) != NULL &&
10977ebbefbeSschwarze 	    nn->type != ROFFT_TEXT && (nn->flags & NODE_LINE) == 0)
10988e60e820Sschwarze 		outflags &= ~MD_spc;
10998e60e820Sschwarze }
11008e60e820Sschwarze 
1101b3257404Sschwarze static int
1102b3257404Sschwarze md_pre_Fn(struct roff_node *n)
1103b3257404Sschwarze {
1104b3257404Sschwarze 	md_pre_syn(n);
1105b3257404Sschwarze 
1106b3257404Sschwarze 	if ((n = n->child) == NULL)
1107b3257404Sschwarze 		return 0;
1108b3257404Sschwarze 
1109b3257404Sschwarze 	md_rawword("**");
1110b3257404Sschwarze 	outflags &= ~MD_spc;
1111b3257404Sschwarze 	md_node(n);
1112b3257404Sschwarze 	outflags &= ~MD_spc;
1113b3257404Sschwarze 	md_rawword("**");
1114b3257404Sschwarze 	outflags &= ~MD_spc;
1115b3257404Sschwarze 	md_word("(");
1116b3257404Sschwarze 
1117b3257404Sschwarze 	if ((n = n->next) != NULL)
1118b3257404Sschwarze 		md_pre_Fa(n);
1119b3257404Sschwarze 	return 0;
1120b3257404Sschwarze }
1121b3257404Sschwarze 
1122b3257404Sschwarze static void
1123b3257404Sschwarze md_post_Fn(struct roff_node *n)
1124b3257404Sschwarze {
1125b3257404Sschwarze 	md_word(")");
1126b3257404Sschwarze 	if (n->flags & NODE_SYNPRETTY) {
1127b3257404Sschwarze 		md_word(";");
1128b3257404Sschwarze 		outflags |= MD_sp;
1129b3257404Sschwarze 	}
1130b3257404Sschwarze }
1131b3257404Sschwarze 
1132b3257404Sschwarze static int
1133b3257404Sschwarze md_pre_Fo(struct roff_node *n)
1134b3257404Sschwarze {
1135b3257404Sschwarze 	switch (n->type) {
1136b3257404Sschwarze 	case ROFFT_BLOCK:
1137b3257404Sschwarze 		md_pre_syn(n);
1138b3257404Sschwarze 		break;
1139b3257404Sschwarze 	case ROFFT_HEAD:
1140b3257404Sschwarze 		if (n->child == NULL)
1141b3257404Sschwarze 			return 0;
1142b3257404Sschwarze 		md_pre_raw(n);
1143b3257404Sschwarze 		break;
1144b3257404Sschwarze 	case ROFFT_BODY:
1145b3257404Sschwarze 		outflags &= ~(MD_spc | MD_nl);
1146b3257404Sschwarze 		md_word("(");
1147b3257404Sschwarze 		break;
1148b3257404Sschwarze 	default:
1149b3257404Sschwarze 		break;
1150b3257404Sschwarze 	}
1151b3257404Sschwarze 	return 1;
1152b3257404Sschwarze }
1153b3257404Sschwarze 
1154b3257404Sschwarze static void
1155b3257404Sschwarze md_post_Fo(struct roff_node *n)
1156b3257404Sschwarze {
1157b3257404Sschwarze 	switch (n->type) {
1158b3257404Sschwarze 	case ROFFT_HEAD:
1159b3257404Sschwarze 		if (n->child != NULL)
1160b3257404Sschwarze 			md_post_raw(n);
1161b3257404Sschwarze 		break;
1162b3257404Sschwarze 	case ROFFT_BODY:
1163b3257404Sschwarze 		md_post_Fn(n);
1164b3257404Sschwarze 		break;
1165b3257404Sschwarze 	default:
1166b3257404Sschwarze 		break;
1167b3257404Sschwarze 	}
1168b3257404Sschwarze }
1169b3257404Sschwarze 
1170b3257404Sschwarze static int
1171b3257404Sschwarze md_pre_In(struct roff_node *n)
1172b3257404Sschwarze {
1173b3257404Sschwarze 	if (n->flags & NODE_SYNPRETTY) {
1174b3257404Sschwarze 		md_pre_syn(n);
11755f580400Sschwarze 		md_rawword("**");
1176b3257404Sschwarze 		outflags &= ~MD_spc;
1177b3257404Sschwarze 		md_word("#include <");
1178b3257404Sschwarze 	} else {
1179b3257404Sschwarze 		md_word("<");
1180b3257404Sschwarze 		outflags &= ~MD_spc;
11815f580400Sschwarze 		md_rawword("*");
1182b3257404Sschwarze 	}
11835f580400Sschwarze 	outflags &= ~MD_spc;
1184b3257404Sschwarze 	return 1;
1185b3257404Sschwarze }
1186b3257404Sschwarze 
1187b3257404Sschwarze static void
1188b3257404Sschwarze md_post_In(struct roff_node *n)
1189b3257404Sschwarze {
1190b3257404Sschwarze 	if (n->flags & NODE_SYNPRETTY) {
1191b3257404Sschwarze 		outflags &= ~MD_spc;
11925f580400Sschwarze 		md_rawword(">**");
1193b3257404Sschwarze 		outflags |= MD_nl;
1194b3257404Sschwarze 	} else {
1195b3257404Sschwarze 		outflags &= ~MD_spc;
11965f580400Sschwarze 		md_rawword("*>");
1197b3257404Sschwarze 	}
1198b3257404Sschwarze }
1199b3257404Sschwarze 
1200b3257404Sschwarze static int
1201b3257404Sschwarze md_pre_It(struct roff_node *n)
1202b3257404Sschwarze {
1203b3257404Sschwarze 	struct roff_node	*bln;
1204b3257404Sschwarze 
1205b3257404Sschwarze 	switch (n->type) {
1206b3257404Sschwarze 	case ROFFT_BLOCK:
1207b3257404Sschwarze 		return 1;
1208b3257404Sschwarze 
1209b3257404Sschwarze 	case ROFFT_HEAD:
1210b3257404Sschwarze 		bln = n->parent->parent;
12119e131d10Sschwarze 		if (bln->norm->Bl.comp == 0 &&
12129e131d10Sschwarze 		    bln->norm->Bl.type != LIST_column)
1213b3257404Sschwarze 			outflags |= MD_sp;
1214b3257404Sschwarze 		outflags |= MD_nl;
1215b3257404Sschwarze 
1216b3257404Sschwarze 		switch (bln->norm->Bl.type) {
1217b3257404Sschwarze 		case LIST_item:
1218b3257404Sschwarze 			outflags |= MD_br;
1219b3257404Sschwarze 			return 0;
1220b3257404Sschwarze 		case LIST_inset:
1221b3257404Sschwarze 		case LIST_diag:
1222b3257404Sschwarze 		case LIST_ohang:
1223b3257404Sschwarze 			outflags |= MD_br;
1224b3257404Sschwarze 			return 1;
1225b3257404Sschwarze 		case LIST_tag:
1226b3257404Sschwarze 		case LIST_hang:
1227b3257404Sschwarze 			outflags |= MD_sp;
1228b3257404Sschwarze 			return 1;
1229b3257404Sschwarze 		case LIST_bullet:
1230b3257404Sschwarze 			md_rawword("*\t");
1231b3257404Sschwarze 			break;
1232b3257404Sschwarze 		case LIST_dash:
1233b3257404Sschwarze 		case LIST_hyphen:
1234b3257404Sschwarze 			md_rawword("-\t");
1235b3257404Sschwarze 			break;
1236b3257404Sschwarze 		case LIST_enum:
1237b3257404Sschwarze 			md_preword();
1238b60e0d16Sschwarze 			if (bln->norm->Bl.count < 99)
1239b60e0d16Sschwarze 				bln->norm->Bl.count++;
1240b60e0d16Sschwarze 			printf("%d.\t", bln->norm->Bl.count);
1241b3257404Sschwarze 			escflags &= ~ESC_FON;
1242b3257404Sschwarze 			break;
12439e131d10Sschwarze 		case LIST_column:
12449e131d10Sschwarze 			outflags |= MD_br;
12459e131d10Sschwarze 			return 0;
1246b3257404Sschwarze 		default:
1247b3257404Sschwarze 			return 0;
1248b3257404Sschwarze 		}
1249b3257404Sschwarze 		outflags &= ~MD_spc;
1250b3257404Sschwarze 		outflags |= MD_nonl;
1251b3257404Sschwarze 		outcount = 0;
1252b3257404Sschwarze 		md_stack('\t');
1253b3257404Sschwarze 		if (code_blocks || quote_blocks)
1254b3257404Sschwarze 			list_blocks++;
1255b3257404Sschwarze 		return 0;
1256b3257404Sschwarze 
1257b3257404Sschwarze 	case ROFFT_BODY:
1258b3257404Sschwarze 		bln = n->parent->parent;
1259b3257404Sschwarze 		switch (bln->norm->Bl.type) {
1260b3257404Sschwarze 		case LIST_ohang:
1261b3257404Sschwarze 			outflags |= MD_br;
1262b3257404Sschwarze 			break;
1263b3257404Sschwarze 		case LIST_tag:
1264b3257404Sschwarze 		case LIST_hang:
1265b3257404Sschwarze 			md_pre_D1(n);
1266b3257404Sschwarze 			break;
1267b3257404Sschwarze 		default:
1268b3257404Sschwarze 			break;
1269b3257404Sschwarze 		}
1270b3257404Sschwarze 		return 1;
1271b3257404Sschwarze 
1272b3257404Sschwarze 	default:
1273b3257404Sschwarze 		return 0;
1274b3257404Sschwarze 	}
1275b3257404Sschwarze }
1276b3257404Sschwarze 
1277b3257404Sschwarze static void
1278b3257404Sschwarze md_post_It(struct roff_node *n)
1279b3257404Sschwarze {
1280b3257404Sschwarze 	struct roff_node	*bln;
1281b3257404Sschwarze 	int			 i, nc;
1282b3257404Sschwarze 
1283b3257404Sschwarze 	if (n->type != ROFFT_BODY)
1284b3257404Sschwarze 		return;
1285b3257404Sschwarze 
1286b3257404Sschwarze 	bln = n->parent->parent;
1287b3257404Sschwarze 	switch (bln->norm->Bl.type) {
1288b3257404Sschwarze 	case LIST_bullet:
1289b3257404Sschwarze 	case LIST_dash:
1290b3257404Sschwarze 	case LIST_hyphen:
1291b3257404Sschwarze 	case LIST_enum:
1292b3257404Sschwarze 		md_stack((char)-1);
1293b3257404Sschwarze 		if (code_blocks || quote_blocks)
1294b3257404Sschwarze 			list_blocks--;
1295b3257404Sschwarze 		break;
1296b3257404Sschwarze 	case LIST_tag:
1297b3257404Sschwarze 	case LIST_hang:
1298b3257404Sschwarze 		md_post_D1(n);
1299b3257404Sschwarze 		break;
1300b3257404Sschwarze 
1301b3257404Sschwarze 	case LIST_column:
1302b3257404Sschwarze 		if (n->next == NULL)
1303b3257404Sschwarze 			break;
1304b3257404Sschwarze 
1305b3257404Sschwarze 		/* Calculate the array index of the current column. */
1306b3257404Sschwarze 
1307b3257404Sschwarze 		i = 0;
1308b3257404Sschwarze 		while ((n = n->prev) != NULL && n->type != ROFFT_HEAD)
1309b3257404Sschwarze 			i++;
1310b3257404Sschwarze 
1311b3257404Sschwarze 		/*
1312b3257404Sschwarze 		 * If a width was specified for this column,
1313b3257404Sschwarze 		 * subtract what printed, and
1314b3257404Sschwarze 		 * add the same spacing as in mdoc_term.c.
1315b3257404Sschwarze 		 */
1316b3257404Sschwarze 
1317b3257404Sschwarze 		nc = bln->norm->Bl.ncols;
1318b3257404Sschwarze 		i = i < nc ? strlen(bln->norm->Bl.cols[i]) - outcount +
1319b3257404Sschwarze 		    (nc < 5 ? 4 : nc == 5 ? 3 : 1) : 1;
1320b3257404Sschwarze 		if (i < 1)
1321b3257404Sschwarze 			i = 1;
1322b3257404Sschwarze 		while (i-- > 0)
1323b3257404Sschwarze 			putchar(' ');
1324b3257404Sschwarze 
1325b3257404Sschwarze 		outflags &= ~MD_spc;
1326b3257404Sschwarze 		escflags &= ~ESC_FON;
1327b3257404Sschwarze 		outcount = 0;
1328b3257404Sschwarze 		break;
1329b3257404Sschwarze 
1330b3257404Sschwarze 	default:
1331b3257404Sschwarze 		break;
1332b3257404Sschwarze 	}
1333b3257404Sschwarze }
1334b3257404Sschwarze 
1335b3257404Sschwarze static void
1336b3257404Sschwarze md_post_Lb(struct roff_node *n)
1337b3257404Sschwarze {
1338b3257404Sschwarze 	if (n->sec == SEC_LIBRARY)
1339b3257404Sschwarze 		outflags |= MD_br;
1340b3257404Sschwarze }
1341b3257404Sschwarze 
134275d8c96aSschwarze static void
134375d8c96aSschwarze md_uri(const char *s)
1344b3257404Sschwarze {
134575d8c96aSschwarze 	while (*s != '\0') {
1346433f0baeSschwarze 		if (strchr("%()<>", *s) != NULL) {
134778ed643eSschwarze 			printf("%%%2.2hhX", *s);
134878ed643eSschwarze 			outcount += 3;
134978ed643eSschwarze 		} else {
135078ed643eSschwarze 			putchar(*s);
135178ed643eSschwarze 			outcount++;
135278ed643eSschwarze 		}
135375d8c96aSschwarze 		s++;
135475d8c96aSschwarze 	}
135578ed643eSschwarze }
135678ed643eSschwarze 
135775d8c96aSschwarze static int
135875d8c96aSschwarze md_pre_Lk(struct roff_node *n)
135975d8c96aSschwarze {
1360eb4d0f30Sschwarze 	const struct roff_node *link, *descr, *punct;
136175d8c96aSschwarze 
136275d8c96aSschwarze 	if ((link = n->child) == NULL)
136375d8c96aSschwarze 		return 0;
136475d8c96aSschwarze 
1365eb4d0f30Sschwarze 	/* Find beginning of trailing punctuation. */
1366eb4d0f30Sschwarze 	punct = n->last;
1367eb4d0f30Sschwarze 	while (punct != link && punct->flags & NODE_DELIMC)
1368eb4d0f30Sschwarze 		punct = punct->prev;
1369eb4d0f30Sschwarze 	punct = punct->next;
1370eb4d0f30Sschwarze 
137196aebfb3Sschwarze 	/* Link text. */
137296aebfb3Sschwarze 	descr = link->next;
1373eb4d0f30Sschwarze 	if (descr == punct)
137496aebfb3Sschwarze 		descr = link;  /* no text */
137575d8c96aSschwarze 	md_rawword("[");
1376b3257404Sschwarze 	outflags &= ~MD_spc;
137775d8c96aSschwarze 	do {
137875d8c96aSschwarze 		md_word(descr->string);
137996aebfb3Sschwarze 		descr = descr->next;
1380eb4d0f30Sschwarze 	} while (descr != punct);
138175d8c96aSschwarze 	outflags &= ~MD_spc;
138296aebfb3Sschwarze 
138396aebfb3Sschwarze 	/* Link target. */
138475d8c96aSschwarze 	md_rawword("](");
138575d8c96aSschwarze 	md_uri(link->string);
138675d8c96aSschwarze 	outflags &= ~MD_spc;
138775d8c96aSschwarze 	md_rawword(")");
138896aebfb3Sschwarze 
138996aebfb3Sschwarze 	/* Trailing punctuation. */
1390eb4d0f30Sschwarze 	while (punct != NULL) {
1391eb4d0f30Sschwarze 		md_word(punct->string);
1392eb4d0f30Sschwarze 		punct = punct->next;
139396aebfb3Sschwarze 	}
139475d8c96aSschwarze 	return 0;
139575d8c96aSschwarze }
139675d8c96aSschwarze 
139775d8c96aSschwarze static int
139875d8c96aSschwarze md_pre_Mt(struct roff_node *n)
139975d8c96aSschwarze {
140075d8c96aSschwarze 	const struct roff_node *nch;
140175d8c96aSschwarze 
140275d8c96aSschwarze 	md_rawword("[");
140375d8c96aSschwarze 	outflags &= ~MD_spc;
140475d8c96aSschwarze 	for (nch = n->child; nch != NULL; nch = nch->next)
140575d8c96aSschwarze 		md_word(nch->string);
140675d8c96aSschwarze 	outflags &= ~MD_spc;
140775d8c96aSschwarze 	md_rawword("](mailto:");
140875d8c96aSschwarze 	for (nch = n->child; nch != NULL; nch = nch->next) {
140975d8c96aSschwarze 		md_uri(nch->string);
141075d8c96aSschwarze 		if (nch->next != NULL) {
141175d8c96aSschwarze 			putchar(' ');
141275d8c96aSschwarze 			outcount++;
141375d8c96aSschwarze 		}
141475d8c96aSschwarze 	}
141575d8c96aSschwarze 	outflags &= ~MD_spc;
141675d8c96aSschwarze 	md_rawword(")");
1417b3257404Sschwarze 	return 0;
1418b3257404Sschwarze }
1419b3257404Sschwarze 
1420b3257404Sschwarze static int
1421b3257404Sschwarze md_pre_Nd(struct roff_node *n)
1422b3257404Sschwarze {
1423b3257404Sschwarze 	outflags &= ~MD_nl;
1424b3257404Sschwarze 	outflags |= MD_spc;
1425b3257404Sschwarze 	md_word("-");
1426b3257404Sschwarze 	return 1;
1427b3257404Sschwarze }
1428b3257404Sschwarze 
1429b3257404Sschwarze static int
1430b3257404Sschwarze md_pre_Nm(struct roff_node *n)
1431b3257404Sschwarze {
1432b3257404Sschwarze 	switch (n->type) {
1433b3257404Sschwarze 	case ROFFT_BLOCK:
1434b3257404Sschwarze 		outflags |= MD_Bk;
1435b3257404Sschwarze 		md_pre_syn(n);
1436b3257404Sschwarze 		break;
1437b3257404Sschwarze 	case ROFFT_HEAD:
1438b3257404Sschwarze 	case ROFFT_ELEM:
1439b3257404Sschwarze 		md_pre_raw(n);
1440b3257404Sschwarze 		break;
1441b3257404Sschwarze 	default:
1442b3257404Sschwarze 		break;
1443b3257404Sschwarze 	}
1444b3257404Sschwarze 	return 1;
1445b3257404Sschwarze }
1446b3257404Sschwarze 
1447b3257404Sschwarze static void
1448b3257404Sschwarze md_post_Nm(struct roff_node *n)
1449b3257404Sschwarze {
1450b3257404Sschwarze 	switch (n->type) {
1451b3257404Sschwarze 	case ROFFT_BLOCK:
1452b3257404Sschwarze 		outflags &= ~MD_Bk;
1453b3257404Sschwarze 		break;
1454b3257404Sschwarze 	case ROFFT_HEAD:
1455b3257404Sschwarze 	case ROFFT_ELEM:
1456b3257404Sschwarze 		md_post_raw(n);
1457b3257404Sschwarze 		break;
1458b3257404Sschwarze 	default:
1459b3257404Sschwarze 		break;
1460b3257404Sschwarze 	}
1461b3257404Sschwarze }
1462b3257404Sschwarze 
1463b3257404Sschwarze static int
1464b3257404Sschwarze md_pre_No(struct roff_node *n)
1465b3257404Sschwarze {
1466b3257404Sschwarze 	outflags |= MD_spc_force;
1467b3257404Sschwarze 	return 1;
1468b3257404Sschwarze }
1469b3257404Sschwarze 
1470b3257404Sschwarze static int
1471b3257404Sschwarze md_pre_Ns(struct roff_node *n)
1472b3257404Sschwarze {
1473b3257404Sschwarze 	outflags &= ~MD_spc;
1474b3257404Sschwarze 	return 0;
1475b3257404Sschwarze }
1476b3257404Sschwarze 
1477b3257404Sschwarze static void
1478b3257404Sschwarze md_post_Pf(struct roff_node *n)
1479b3257404Sschwarze {
1480b3257404Sschwarze 	if (n->next != NULL && (n->next->flags & NODE_LINE) == 0)
1481b3257404Sschwarze 		outflags &= ~MD_spc;
1482b3257404Sschwarze }
1483b3257404Sschwarze 
1484b3257404Sschwarze static int
1485b3257404Sschwarze md_pre_Pp(struct roff_node *n)
1486b3257404Sschwarze {
1487b3257404Sschwarze 	outflags |= MD_sp;
1488b3257404Sschwarze 	return 0;
1489b3257404Sschwarze }
1490b3257404Sschwarze 
1491b3257404Sschwarze static int
1492b3257404Sschwarze md_pre_Rs(struct roff_node *n)
1493b3257404Sschwarze {
1494b3257404Sschwarze 	if (n->sec == SEC_SEE_ALSO)
1495b3257404Sschwarze 		outflags |= MD_sp;
1496b3257404Sschwarze 	return 1;
1497b3257404Sschwarze }
1498b3257404Sschwarze 
1499b3257404Sschwarze static int
1500b3257404Sschwarze md_pre_Sh(struct roff_node *n)
1501b3257404Sschwarze {
1502b3257404Sschwarze 	switch (n->type) {
15032fe9aa39Sschwarze 	case ROFFT_BLOCK:
15042fe9aa39Sschwarze 		if (n->sec == SEC_AUTHORS)
15052fe9aa39Sschwarze 			outflags &= ~(MD_An_split | MD_An_nosplit);
15062fe9aa39Sschwarze 		break;
1507b3257404Sschwarze 	case ROFFT_HEAD:
1508b3257404Sschwarze 		outflags |= MD_sp;
1509b3257404Sschwarze 		md_rawword(n->tok == MDOC_Sh ? "#" : "##");
1510b3257404Sschwarze 		break;
1511b3257404Sschwarze 	case ROFFT_BODY:
1512b3257404Sschwarze 		outflags |= MD_sp;
1513b3257404Sschwarze 		break;
1514b3257404Sschwarze 	default:
1515b3257404Sschwarze 		break;
1516b3257404Sschwarze 	}
1517b3257404Sschwarze 	return 1;
1518b3257404Sschwarze }
1519b3257404Sschwarze 
1520b3257404Sschwarze static int
1521b3257404Sschwarze md_pre_Sm(struct roff_node *n)
1522b3257404Sschwarze {
1523b3257404Sschwarze 	if (n->child == NULL)
1524b3257404Sschwarze 		outflags ^= MD_Sm;
1525b3257404Sschwarze 	else if (strcmp("on", n->child->string) == 0)
1526b3257404Sschwarze 		outflags |= MD_Sm;
1527b3257404Sschwarze 	else
1528b3257404Sschwarze 		outflags &= ~MD_Sm;
1529b3257404Sschwarze 
1530b3257404Sschwarze 	if (outflags & MD_Sm)
1531b3257404Sschwarze 		outflags |= MD_spc;
1532b3257404Sschwarze 
1533b3257404Sschwarze 	return 0;
1534b3257404Sschwarze }
1535b3257404Sschwarze 
1536b3257404Sschwarze static int
1537b3257404Sschwarze md_pre_Vt(struct roff_node *n)
1538b3257404Sschwarze {
1539b3257404Sschwarze 	switch (n->type) {
1540b3257404Sschwarze 	case ROFFT_BLOCK:
1541b3257404Sschwarze 		md_pre_syn(n);
1542b3257404Sschwarze 		return 1;
1543b3257404Sschwarze 	case ROFFT_BODY:
1544b3257404Sschwarze 	case ROFFT_ELEM:
1545b3257404Sschwarze 		md_pre_raw(n);
1546b3257404Sschwarze 		return 1;
1547b3257404Sschwarze 	default:
1548b3257404Sschwarze 		return 0;
1549b3257404Sschwarze 	}
1550b3257404Sschwarze }
1551b3257404Sschwarze 
1552b3257404Sschwarze static void
1553b3257404Sschwarze md_post_Vt(struct roff_node *n)
1554b3257404Sschwarze {
1555b3257404Sschwarze 	switch (n->type) {
1556b3257404Sschwarze 	case ROFFT_BODY:
1557b3257404Sschwarze 	case ROFFT_ELEM:
1558b3257404Sschwarze 		md_post_raw(n);
1559b3257404Sschwarze 		break;
1560b3257404Sschwarze 	default:
1561b3257404Sschwarze 		break;
1562b3257404Sschwarze 	}
1563b3257404Sschwarze }
1564b3257404Sschwarze 
1565b3257404Sschwarze static int
1566b3257404Sschwarze md_pre_Xr(struct roff_node *n)
1567b3257404Sschwarze {
1568b3257404Sschwarze 	n = n->child;
1569b3257404Sschwarze 	if (n == NULL)
1570b3257404Sschwarze 		return 0;
1571b3257404Sschwarze 	md_node(n);
1572b3257404Sschwarze 	n = n->next;
1573b3257404Sschwarze 	if (n == NULL)
1574b3257404Sschwarze 		return 0;
1575b3257404Sschwarze 	outflags &= ~MD_spc;
1576b3257404Sschwarze 	md_word("(");
1577b3257404Sschwarze 	md_node(n);
1578b3257404Sschwarze 	md_word(")");
1579b3257404Sschwarze 	return 0;
1580b3257404Sschwarze }
1581b3257404Sschwarze 
1582b3257404Sschwarze static int
1583*bcf2ba68Sschwarze md_pre__R(struct roff_node *n)
1584*bcf2ba68Sschwarze {
1585*bcf2ba68Sschwarze 	const unsigned char	*cp;
1586*bcf2ba68Sschwarze 	const char		*arg;
1587*bcf2ba68Sschwarze 
1588*bcf2ba68Sschwarze 	arg = n->child->string;
1589*bcf2ba68Sschwarze 
1590*bcf2ba68Sschwarze 	if (strncmp(arg, "RFC ", 4) != 0)
1591*bcf2ba68Sschwarze 		return 1;
1592*bcf2ba68Sschwarze 	cp = arg += 4;
1593*bcf2ba68Sschwarze 	while (isdigit(*cp))
1594*bcf2ba68Sschwarze 		cp++;
1595*bcf2ba68Sschwarze 	if (*cp != '\0')
1596*bcf2ba68Sschwarze 		return 1;
1597*bcf2ba68Sschwarze 
1598*bcf2ba68Sschwarze 	md_rawword("[RFC ");
1599*bcf2ba68Sschwarze 	outflags &= ~MD_spc;
1600*bcf2ba68Sschwarze 	md_rawword(arg);
1601*bcf2ba68Sschwarze 	outflags &= ~MD_spc;
1602*bcf2ba68Sschwarze 	md_rawword("](http://www.rfc-editor.org/rfc/rfc");
1603*bcf2ba68Sschwarze 	outflags &= ~MD_spc;
1604*bcf2ba68Sschwarze 	md_rawword(arg);
1605*bcf2ba68Sschwarze 	outflags &= ~MD_spc;
1606*bcf2ba68Sschwarze 	md_rawword(".html)");
1607*bcf2ba68Sschwarze 	return 0;
1608*bcf2ba68Sschwarze }
1609*bcf2ba68Sschwarze 
1610*bcf2ba68Sschwarze static int
1611b3257404Sschwarze md_pre__T(struct roff_node *n)
1612b3257404Sschwarze {
16136b7d675aSschwarze 	if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
1614b3257404Sschwarze 		md_word("\"");
1615b3257404Sschwarze 	else
1616b3257404Sschwarze 		md_rawword("*");
1617b3257404Sschwarze 	outflags &= ~MD_spc;
1618b3257404Sschwarze 	return 1;
1619b3257404Sschwarze }
1620b3257404Sschwarze 
1621b3257404Sschwarze static void
1622b3257404Sschwarze md_post__T(struct roff_node *n)
1623b3257404Sschwarze {
1624b3257404Sschwarze 	outflags &= ~MD_spc;
16256b7d675aSschwarze 	if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
1626b3257404Sschwarze 		md_word("\"");
1627b3257404Sschwarze 	else
1628b3257404Sschwarze 		md_rawword("*");
1629b3257404Sschwarze 	md_post_pc(n);
1630b3257404Sschwarze }
1631b3257404Sschwarze 
1632b3257404Sschwarze static int
1633b3257404Sschwarze md_pre_br(struct roff_node *n)
1634b3257404Sschwarze {
1635b3257404Sschwarze 	outflags |= MD_br;
1636b3257404Sschwarze 	return 0;
1637b3257404Sschwarze }
1638