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