xref: /dragonfly/contrib/mdocml/mdoc_term.c (revision fcf53d9b)
1 /*	$Id: mdoc_term.c,v 1.226 2011/04/04 16:27:03 kristaps Exp $ */
2 /*
3  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 
22 #include <sys/types.h>
23 
24 #include <assert.h>
25 #include <ctype.h>
26 #include <stdint.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #include "mandoc.h"
32 #include "out.h"
33 #include "term.h"
34 #include "mdoc.h"
35 #include "main.h"
36 
37 #define	INDENT		  5
38 #define	HALFINDENT	  3
39 
40 struct	termpair {
41 	struct termpair	 *ppair;
42 	int		  count;
43 };
44 
45 #define	DECL_ARGS struct termp *p, \
46 		  struct termpair *pair, \
47 	  	  const struct mdoc_meta *m, \
48 		  const struct mdoc_node *n
49 
50 struct	termact {
51 	int	(*pre)(DECL_ARGS);
52 	void	(*post)(DECL_ARGS);
53 };
54 
55 static	size_t	  a2width(const struct termp *, const char *);
56 static	size_t	  a2height(const struct termp *, const char *);
57 static	size_t	  a2offs(const struct termp *, const char *);
58 
59 static	void	  print_bvspace(struct termp *,
60 			const struct mdoc_node *,
61 			const struct mdoc_node *);
62 static	void  	  print_mdoc_node(DECL_ARGS);
63 static	void	  print_mdoc_nodelist(DECL_ARGS);
64 static	void	  print_mdoc_head(struct termp *, const void *);
65 static	void	  print_mdoc_foot(struct termp *, const void *);
66 static	void	  synopsis_pre(struct termp *,
67 			const struct mdoc_node *);
68 
69 static	void	  termp____post(DECL_ARGS);
70 static	void	  termp__t_post(DECL_ARGS);
71 static	void	  termp_an_post(DECL_ARGS);
72 static	void	  termp_bd_post(DECL_ARGS);
73 static	void	  termp_bk_post(DECL_ARGS);
74 static	void	  termp_bl_post(DECL_ARGS);
75 static	void	  termp_d1_post(DECL_ARGS);
76 static	void	  termp_fo_post(DECL_ARGS);
77 static	void	  termp_in_post(DECL_ARGS);
78 static	void	  termp_it_post(DECL_ARGS);
79 static	void	  termp_lb_post(DECL_ARGS);
80 static	void	  termp_nm_post(DECL_ARGS);
81 static	void	  termp_pf_post(DECL_ARGS);
82 static	void	  termp_quote_post(DECL_ARGS);
83 static	void	  termp_sh_post(DECL_ARGS);
84 static	void	  termp_ss_post(DECL_ARGS);
85 
86 static	int	  termp__a_pre(DECL_ARGS);
87 static	int	  termp__t_pre(DECL_ARGS);
88 static	int	  termp_an_pre(DECL_ARGS);
89 static	int	  termp_ap_pre(DECL_ARGS);
90 static	int	  termp_bd_pre(DECL_ARGS);
91 static	int	  termp_bf_pre(DECL_ARGS);
92 static	int	  termp_bk_pre(DECL_ARGS);
93 static	int	  termp_bl_pre(DECL_ARGS);
94 static	int	  termp_bold_pre(DECL_ARGS);
95 static	int	  termp_bt_pre(DECL_ARGS);
96 static	int	  termp_bx_pre(DECL_ARGS);
97 static	int	  termp_cd_pre(DECL_ARGS);
98 static	int	  termp_d1_pre(DECL_ARGS);
99 static	int	  termp_ex_pre(DECL_ARGS);
100 static	int	  termp_fa_pre(DECL_ARGS);
101 static	int	  termp_fd_pre(DECL_ARGS);
102 static	int	  termp_fl_pre(DECL_ARGS);
103 static	int	  termp_fn_pre(DECL_ARGS);
104 static	int	  termp_fo_pre(DECL_ARGS);
105 static	int	  termp_ft_pre(DECL_ARGS);
106 static	int	  termp_igndelim_pre(DECL_ARGS);
107 static	int	  termp_in_pre(DECL_ARGS);
108 static	int	  termp_it_pre(DECL_ARGS);
109 static	int	  termp_li_pre(DECL_ARGS);
110 static	int	  termp_lk_pre(DECL_ARGS);
111 static	int	  termp_nd_pre(DECL_ARGS);
112 static	int	  termp_nm_pre(DECL_ARGS);
113 static	int	  termp_ns_pre(DECL_ARGS);
114 static	int	  termp_quote_pre(DECL_ARGS);
115 static	int	  termp_rs_pre(DECL_ARGS);
116 static	int	  termp_rv_pre(DECL_ARGS);
117 static	int	  termp_sh_pre(DECL_ARGS);
118 static	int	  termp_sm_pre(DECL_ARGS);
119 static	int	  termp_sp_pre(DECL_ARGS);
120 static	int	  termp_ss_pre(DECL_ARGS);
121 static	int	  termp_under_pre(DECL_ARGS);
122 static	int	  termp_ud_pre(DECL_ARGS);
123 static	int	  termp_vt_pre(DECL_ARGS);
124 static	int	  termp_xr_pre(DECL_ARGS);
125 static	int	  termp_xx_pre(DECL_ARGS);
126 
127 static	const struct termact termacts[MDOC_MAX] = {
128 	{ termp_ap_pre, NULL }, /* Ap */
129 	{ NULL, NULL }, /* Dd */
130 	{ NULL, NULL }, /* Dt */
131 	{ NULL, NULL }, /* Os */
132 	{ termp_sh_pre, termp_sh_post }, /* Sh */
133 	{ termp_ss_pre, termp_ss_post }, /* Ss */
134 	{ termp_sp_pre, NULL }, /* Pp */
135 	{ termp_d1_pre, termp_d1_post }, /* D1 */
136 	{ termp_d1_pre, termp_d1_post }, /* Dl */
137 	{ termp_bd_pre, termp_bd_post }, /* Bd */
138 	{ NULL, NULL }, /* Ed */
139 	{ termp_bl_pre, termp_bl_post }, /* Bl */
140 	{ NULL, NULL }, /* El */
141 	{ termp_it_pre, termp_it_post }, /* It */
142 	{ termp_under_pre, NULL }, /* Ad */
143 	{ termp_an_pre, termp_an_post }, /* An */
144 	{ termp_under_pre, NULL }, /* Ar */
145 	{ termp_cd_pre, NULL }, /* Cd */
146 	{ termp_bold_pre, NULL }, /* Cm */
147 	{ NULL, NULL }, /* Dv */
148 	{ NULL, NULL }, /* Er */
149 	{ NULL, NULL }, /* Ev */
150 	{ termp_ex_pre, NULL }, /* Ex */
151 	{ termp_fa_pre, NULL }, /* Fa */
152 	{ termp_fd_pre, NULL }, /* Fd */
153 	{ termp_fl_pre, NULL }, /* Fl */
154 	{ termp_fn_pre, NULL }, /* Fn */
155 	{ termp_ft_pre, NULL }, /* Ft */
156 	{ termp_bold_pre, NULL }, /* Ic */
157 	{ termp_in_pre, termp_in_post }, /* In */
158 	{ termp_li_pre, NULL }, /* Li */
159 	{ termp_nd_pre, NULL }, /* Nd */
160 	{ termp_nm_pre, termp_nm_post }, /* Nm */
161 	{ termp_quote_pre, termp_quote_post }, /* Op */
162 	{ NULL, NULL }, /* Ot */
163 	{ termp_under_pre, NULL }, /* Pa */
164 	{ termp_rv_pre, NULL }, /* Rv */
165 	{ NULL, NULL }, /* St */
166 	{ termp_under_pre, NULL }, /* Va */
167 	{ termp_vt_pre, NULL }, /* Vt */
168 	{ termp_xr_pre, NULL }, /* Xr */
169 	{ termp__a_pre, termp____post }, /* %A */
170 	{ termp_under_pre, termp____post }, /* %B */
171 	{ NULL, termp____post }, /* %D */
172 	{ termp_under_pre, termp____post }, /* %I */
173 	{ termp_under_pre, termp____post }, /* %J */
174 	{ NULL, termp____post }, /* %N */
175 	{ NULL, termp____post }, /* %O */
176 	{ NULL, termp____post }, /* %P */
177 	{ NULL, termp____post }, /* %R */
178 	{ termp__t_pre, termp__t_post }, /* %T */
179 	{ NULL, termp____post }, /* %V */
180 	{ NULL, NULL }, /* Ac */
181 	{ termp_quote_pre, termp_quote_post }, /* Ao */
182 	{ termp_quote_pre, termp_quote_post }, /* Aq */
183 	{ NULL, NULL }, /* At */
184 	{ NULL, NULL }, /* Bc */
185 	{ termp_bf_pre, NULL }, /* Bf */
186 	{ termp_quote_pre, termp_quote_post }, /* Bo */
187 	{ termp_quote_pre, termp_quote_post }, /* Bq */
188 	{ termp_xx_pre, NULL }, /* Bsx */
189 	{ termp_bx_pre, NULL }, /* Bx */
190 	{ NULL, NULL }, /* Db */
191 	{ NULL, NULL }, /* Dc */
192 	{ termp_quote_pre, termp_quote_post }, /* Do */
193 	{ termp_quote_pre, termp_quote_post }, /* Dq */
194 	{ NULL, NULL }, /* Ec */ /* FIXME: no space */
195 	{ NULL, NULL }, /* Ef */
196 	{ termp_under_pre, NULL }, /* Em */
197 	{ NULL, NULL }, /* Eo */
198 	{ termp_xx_pre, NULL }, /* Fx */
199 	{ termp_bold_pre, NULL }, /* Ms */
200 	{ termp_igndelim_pre, NULL }, /* No */
201 	{ termp_ns_pre, NULL }, /* Ns */
202 	{ termp_xx_pre, NULL }, /* Nx */
203 	{ termp_xx_pre, NULL }, /* Ox */
204 	{ NULL, NULL }, /* Pc */
205 	{ termp_igndelim_pre, termp_pf_post }, /* Pf */
206 	{ termp_quote_pre, termp_quote_post }, /* Po */
207 	{ termp_quote_pre, termp_quote_post }, /* Pq */
208 	{ NULL, NULL }, /* Qc */
209 	{ termp_quote_pre, termp_quote_post }, /* Ql */
210 	{ termp_quote_pre, termp_quote_post }, /* Qo */
211 	{ termp_quote_pre, termp_quote_post }, /* Qq */
212 	{ NULL, NULL }, /* Re */
213 	{ termp_rs_pre, NULL }, /* Rs */
214 	{ NULL, NULL }, /* Sc */
215 	{ termp_quote_pre, termp_quote_post }, /* So */
216 	{ termp_quote_pre, termp_quote_post }, /* Sq */
217 	{ termp_sm_pre, NULL }, /* Sm */
218 	{ termp_under_pre, NULL }, /* Sx */
219 	{ termp_bold_pre, NULL }, /* Sy */
220 	{ NULL, NULL }, /* Tn */
221 	{ termp_xx_pre, NULL }, /* Ux */
222 	{ NULL, NULL }, /* Xc */
223 	{ NULL, NULL }, /* Xo */
224 	{ termp_fo_pre, termp_fo_post }, /* Fo */
225 	{ NULL, NULL }, /* Fc */
226 	{ termp_quote_pre, termp_quote_post }, /* Oo */
227 	{ NULL, NULL }, /* Oc */
228 	{ termp_bk_pre, termp_bk_post }, /* Bk */
229 	{ NULL, NULL }, /* Ek */
230 	{ termp_bt_pre, NULL }, /* Bt */
231 	{ NULL, NULL }, /* Hf */
232 	{ NULL, NULL }, /* Fr */
233 	{ termp_ud_pre, NULL }, /* Ud */
234 	{ NULL, termp_lb_post }, /* Lb */
235 	{ termp_sp_pre, NULL }, /* Lp */
236 	{ termp_lk_pre, NULL }, /* Lk */
237 	{ termp_under_pre, NULL }, /* Mt */
238 	{ termp_quote_pre, termp_quote_post }, /* Brq */
239 	{ termp_quote_pre, termp_quote_post }, /* Bro */
240 	{ NULL, NULL }, /* Brc */
241 	{ NULL, termp____post }, /* %C */
242 	{ NULL, NULL }, /* Es */ /* TODO */
243 	{ NULL, NULL }, /* En */ /* TODO */
244 	{ termp_xx_pre, NULL }, /* Dx */
245 	{ NULL, termp____post }, /* %Q */
246 	{ termp_sp_pre, NULL }, /* br */
247 	{ termp_sp_pre, NULL }, /* sp */
248 	{ termp_under_pre, termp____post }, /* %U */
249 	{ NULL, NULL }, /* Ta */
250 };
251 
252 
253 void
254 terminal_mdoc(void *arg, const struct mdoc *mdoc)
255 {
256 	const struct mdoc_node	*n;
257 	const struct mdoc_meta	*m;
258 	struct termp		*p;
259 
260 	p = (struct termp *)arg;
261 
262 	p->overstep = 0;
263 	p->maxrmargin = p->defrmargin;
264 	p->tabwidth = term_len(p, 5);
265 
266 	if (NULL == p->symtab)
267 		switch (p->enc) {
268 		case (TERMENC_ASCII):
269 			p->symtab = chars_init(CHARS_ASCII);
270 			break;
271 		default:
272 			abort();
273 			/* NOTREACHED */
274 		}
275 
276 	n = mdoc_node(mdoc);
277 	m = mdoc_meta(mdoc);
278 
279 	term_begin(p, print_mdoc_head, print_mdoc_foot, m);
280 
281 	if (n->child)
282 		print_mdoc_nodelist(p, NULL, m, n->child);
283 
284 	term_end(p);
285 }
286 
287 
288 static void
289 print_mdoc_nodelist(DECL_ARGS)
290 {
291 
292 	print_mdoc_node(p, pair, m, n);
293 	if (n->next)
294 		print_mdoc_nodelist(p, pair, m, n->next);
295 }
296 
297 
298 /* ARGSUSED */
299 static void
300 print_mdoc_node(DECL_ARGS)
301 {
302 	int		 chld;
303 	const void	*font;
304 	struct termpair	 npair;
305 	size_t		 offset, rmargin;
306 
307 	chld = 1;
308 	offset = p->offset;
309 	rmargin = p->rmargin;
310 	font = term_fontq(p);
311 
312 	memset(&npair, 0, sizeof(struct termpair));
313 	npair.ppair = pair;
314 
315 	/*
316 	 * Keeps only work until the end of a line.  If a keep was
317 	 * invoked in a prior line, revert it to PREKEEP.
318 	 *
319 	 * Also let SYNPRETTY sections behave as if they were wrapped
320 	 * in a `Bk' block.
321 	 */
322 
323 	if (TERMP_KEEP & p->flags || MDOC_SYNPRETTY & n->flags) {
324 		if (n->prev && n->prev->line != n->line) {
325 			p->flags &= ~TERMP_KEEP;
326 			p->flags |= TERMP_PREKEEP;
327 		} else if (NULL == n->prev) {
328 			if (n->parent && n->parent->line != n->line) {
329 				p->flags &= ~TERMP_KEEP;
330 				p->flags |= TERMP_PREKEEP;
331 			}
332 		}
333 	}
334 
335 	/*
336 	 * Since SYNPRETTY sections aren't "turned off" with `Ek',
337 	 * we have to intuit whether we should disable formatting.
338 	 */
339 
340 	if ( ! (MDOC_SYNPRETTY & n->flags) &&
341 	    ((n->prev   && MDOC_SYNPRETTY & n->prev->flags) ||
342 	     (n->parent && MDOC_SYNPRETTY & n->parent->flags)))
343 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
344 
345 	/*
346 	 * After the keep flags have been set up, we may now
347 	 * produce output.  Note that some pre-handlers do so.
348 	 */
349 
350 	switch (n->type) {
351 	case (MDOC_TEXT):
352 		if (' ' == *n->string && MDOC_LINE & n->flags)
353 			term_newln(p);
354 		if (MDOC_DELIMC & n->flags)
355 			p->flags |= TERMP_NOSPACE;
356 		term_word(p, n->string);
357 		if (MDOC_DELIMO & n->flags)
358 			p->flags |= TERMP_NOSPACE;
359 		break;
360 	case (MDOC_EQN):
361 		term_word(p, n->eqn->data);
362 		break;
363 	case (MDOC_TBL):
364 		term_tbl(p, n->span);
365 		break;
366 	default:
367 		if (termacts[n->tok].pre && ENDBODY_NOT == n->end)
368 			chld = (*termacts[n->tok].pre)
369 				(p, &npair, m, n);
370 		break;
371 	}
372 
373 	if (chld && n->child)
374 		print_mdoc_nodelist(p, &npair, m, n->child);
375 
376 	term_fontpopq(p, font);
377 
378 	switch (n->type) {
379 	case (MDOC_TEXT):
380 		break;
381 	case (MDOC_TBL):
382 		break;
383 	case (MDOC_EQN):
384 		break;
385 	default:
386 		if ( ! termacts[n->tok].post || MDOC_ENDED & n->flags)
387 			break;
388 		(void)(*termacts[n->tok].post)(p, &npair, m, n);
389 
390 		/*
391 		 * Explicit end tokens not only call the post
392 		 * handler, but also tell the respective block
393 		 * that it must not call the post handler again.
394 		 */
395 		if (ENDBODY_NOT != n->end)
396 			n->pending->flags |= MDOC_ENDED;
397 
398 		/*
399 		 * End of line terminating an implicit block
400 		 * while an explicit block is still open.
401 		 * Continue the explicit block without spacing.
402 		 */
403 		if (ENDBODY_NOSPACE == n->end)
404 			p->flags |= TERMP_NOSPACE;
405 		break;
406 	}
407 
408 	if (MDOC_EOS & n->flags)
409 		p->flags |= TERMP_SENTENCE;
410 
411 	p->offset = offset;
412 	p->rmargin = rmargin;
413 }
414 
415 
416 static void
417 print_mdoc_foot(struct termp *p, const void *arg)
418 {
419 	const struct mdoc_meta *m;
420 
421 	m = (const struct mdoc_meta *)arg;
422 
423 	term_fontrepl(p, TERMFONT_NONE);
424 
425 	/*
426 	 * Output the footer in new-groff style, that is, three columns
427 	 * with the middle being the manual date and flanking columns
428 	 * being the operating system:
429 	 *
430 	 * SYSTEM                  DATE                    SYSTEM
431 	 */
432 
433 	term_vspace(p);
434 
435 	p->offset = 0;
436 	p->rmargin = (p->maxrmargin -
437 			term_strlen(p, m->date) + term_len(p, 1)) / 2;
438 	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
439 
440 	term_word(p, m->os);
441 	term_flushln(p);
442 
443 	p->offset = p->rmargin;
444 	p->rmargin = p->maxrmargin - term_strlen(p, m->os);
445 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
446 
447 	term_word(p, m->date);
448 	term_flushln(p);
449 
450 	p->offset = p->rmargin;
451 	p->rmargin = p->maxrmargin;
452 	p->flags &= ~TERMP_NOBREAK;
453 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
454 
455 	term_word(p, m->os);
456 	term_flushln(p);
457 
458 	p->offset = 0;
459 	p->rmargin = p->maxrmargin;
460 	p->flags = 0;
461 }
462 
463 
464 static void
465 print_mdoc_head(struct termp *p, const void *arg)
466 {
467 	char		buf[BUFSIZ], title[BUFSIZ];
468 	const struct mdoc_meta *m;
469 
470 	m = (const struct mdoc_meta *)arg;
471 
472 	p->rmargin = p->maxrmargin;
473 	p->offset = 0;
474 
475 	/*
476 	 * The header is strange.  It has three components, which are
477 	 * really two with the first duplicated.  It goes like this:
478 	 *
479 	 * IDENTIFIER              TITLE                   IDENTIFIER
480 	 *
481 	 * The IDENTIFIER is NAME(SECTION), which is the command-name
482 	 * (if given, or "unknown" if not) followed by the manual page
483 	 * section.  These are given in `Dt'.  The TITLE is a free-form
484 	 * string depending on the manual volume.  If not specified, it
485 	 * switches on the manual section.
486 	 */
487 
488 	assert(m->vol);
489 	strlcpy(buf, m->vol, BUFSIZ);
490 
491 	if (m->arch) {
492 		strlcat(buf, " (", BUFSIZ);
493 		strlcat(buf, m->arch, BUFSIZ);
494 		strlcat(buf, ")", BUFSIZ);
495 	}
496 
497 	snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec);
498 
499 	p->offset = 0;
500 	p->rmargin = (p->maxrmargin -
501 			term_strlen(p, buf) + term_len(p, 1)) / 2;
502 	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
503 
504 	term_word(p, title);
505 	term_flushln(p);
506 
507 	p->offset = p->rmargin;
508 	p->rmargin = p->maxrmargin - term_strlen(p, title);
509 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
510 
511 	term_word(p, buf);
512 	term_flushln(p);
513 
514 	p->offset = p->rmargin;
515 	p->rmargin = p->maxrmargin;
516 	p->flags &= ~TERMP_NOBREAK;
517 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
518 
519 	term_word(p, title);
520 	term_flushln(p);
521 
522 	p->offset = 0;
523 	p->rmargin = p->maxrmargin;
524 	p->flags &= ~TERMP_NOSPACE;
525 }
526 
527 
528 static size_t
529 a2height(const struct termp *p, const char *v)
530 {
531 	struct roffsu	 su;
532 
533 	assert(v);
534 	if ( ! a2roffsu(v, &su, SCALE_VS))
535 		SCALE_VS_INIT(&su, term_len(p, 1));
536 
537 	return(term_vspan(p, &su));
538 }
539 
540 
541 static size_t
542 a2width(const struct termp *p, const char *v)
543 {
544 	struct roffsu	 su;
545 
546 	assert(v);
547 	if ( ! a2roffsu(v, &su, SCALE_MAX))
548 		SCALE_HS_INIT(&su, term_strlen(p, v));
549 
550 	return(term_hspan(p, &su));
551 }
552 
553 
554 static size_t
555 a2offs(const struct termp *p, const char *v)
556 {
557 	struct roffsu	 su;
558 
559 	if ('\0' == *v)
560 		return(0);
561 	else if (0 == strcmp(v, "left"))
562 		return(0);
563 	else if (0 == strcmp(v, "indent"))
564 		return(term_len(p, INDENT + 1));
565 	else if (0 == strcmp(v, "indent-two"))
566 		return(term_len(p, (INDENT + 1) * 2));
567 	else if ( ! a2roffsu(v, &su, SCALE_MAX))
568 		SCALE_HS_INIT(&su, term_strlen(p, v));
569 
570 	return(term_hspan(p, &su));
571 }
572 
573 
574 /*
575  * Determine how much space to print out before block elements of `It'
576  * (and thus `Bl') and `Bd'.  And then go ahead and print that space,
577  * too.
578  */
579 static void
580 print_bvspace(struct termp *p,
581 		const struct mdoc_node *bl,
582 		const struct mdoc_node *n)
583 {
584 	const struct mdoc_node	*nn;
585 
586 	term_newln(p);
587 
588 	if (MDOC_Bd == bl->tok && bl->norm->Bd.comp)
589 		return;
590 	if (MDOC_Bl == bl->tok && bl->norm->Bl.comp)
591 		return;
592 
593 	/* Do not vspace directly after Ss/Sh. */
594 
595 	for (nn = n; nn; nn = nn->parent) {
596 		if (MDOC_BLOCK != nn->type)
597 			continue;
598 		if (MDOC_Ss == nn->tok)
599 			return;
600 		if (MDOC_Sh == nn->tok)
601 			return;
602 		if (NULL == nn->prev)
603 			continue;
604 		break;
605 	}
606 
607 	/* A `-column' does not assert vspace within the list. */
608 
609 	if (MDOC_Bl == bl->tok && LIST_column == bl->norm->Bl.type)
610 		if (n->prev && MDOC_It == n->prev->tok)
611 			return;
612 
613 	/* A `-diag' without body does not vspace. */
614 
615 	if (MDOC_Bl == bl->tok && LIST_diag == bl->norm->Bl.type)
616 		if (n->prev && MDOC_It == n->prev->tok) {
617 			assert(n->prev->body);
618 			if (NULL == n->prev->body->child)
619 				return;
620 		}
621 
622 	term_vspace(p);
623 }
624 
625 
626 /* ARGSUSED */
627 static int
628 termp_it_pre(DECL_ARGS)
629 {
630 	const struct mdoc_node *bl, *nn;
631 	char		        buf[7];
632 	int		        i;
633 	size_t		        width, offset, ncols, dcol;
634 	enum mdoc_list		type;
635 
636 	if (MDOC_BLOCK == n->type) {
637 		print_bvspace(p, n->parent->parent, n);
638 		return(1);
639 	}
640 
641 	bl = n->parent->parent->parent;
642 	type = bl->norm->Bl.type;
643 
644 	/*
645 	 * First calculate width and offset.  This is pretty easy unless
646 	 * we're a -column list, in which case all prior columns must
647 	 * be accounted for.
648 	 */
649 
650 	width = offset = 0;
651 
652 	if (bl->norm->Bl.offs)
653 		offset = a2offs(p, bl->norm->Bl.offs);
654 
655 	switch (type) {
656 	case (LIST_column):
657 		if (MDOC_HEAD == n->type)
658 			break;
659 
660 		/*
661 		 * Imitate groff's column handling:
662 		 * - For each earlier column, add its width.
663 		 * - For less than 5 columns, add four more blanks per
664 		 *   column.
665 		 * - For exactly 5 columns, add three more blank per
666 		 *   column.
667 		 * - For more than 5 columns, add only one column.
668 		 */
669 		ncols = bl->norm->Bl.ncols;
670 
671 		/* LINTED */
672 		dcol = ncols < 5 ? term_len(p, 4) :
673 			ncols == 5 ? term_len(p, 3) : term_len(p, 1);
674 
675 		/*
676 		 * Calculate the offset by applying all prior MDOC_BODY,
677 		 * so we stop at the MDOC_HEAD (NULL == nn->prev).
678 		 */
679 
680 		for (i = 0, nn = n->prev;
681 				nn->prev && i < (int)ncols;
682 				nn = nn->prev, i++)
683 			offset += dcol + a2width
684 				(p, bl->norm->Bl.cols[i]);
685 
686 		/*
687 		 * When exceeding the declared number of columns, leave
688 		 * the remaining widths at 0.  This will later be
689 		 * adjusted to the default width of 10, or, for the last
690 		 * column, stretched to the right margin.
691 		 */
692 		if (i >= (int)ncols)
693 			break;
694 
695 		/*
696 		 * Use the declared column widths, extended as explained
697 		 * in the preceding paragraph.
698 		 */
699 		width = a2width(p, bl->norm->Bl.cols[i]) + dcol;
700 		break;
701 	default:
702 		if (NULL == bl->norm->Bl.width)
703 			break;
704 
705 		/*
706 		 * Note: buffer the width by 2, which is groff's magic
707 		 * number for buffering single arguments.  See the above
708 		 * handling for column for how this changes.
709 		 */
710 		assert(bl->norm->Bl.width);
711 		width = a2width(p, bl->norm->Bl.width) + term_len(p, 2);
712 		break;
713 	}
714 
715 	/*
716 	 * List-type can override the width in the case of fixed-head
717 	 * values (bullet, dash/hyphen, enum).  Tags need a non-zero
718 	 * offset.
719 	 */
720 
721 	switch (type) {
722 	case (LIST_bullet):
723 		/* FALLTHROUGH */
724 	case (LIST_dash):
725 		/* FALLTHROUGH */
726 	case (LIST_hyphen):
727 		if (width < term_len(p, 4))
728 			width = term_len(p, 4);
729 		break;
730 	case (LIST_enum):
731 		if (width < term_len(p, 5))
732 			width = term_len(p, 5);
733 		break;
734 	case (LIST_hang):
735 		if (0 == width)
736 			width = term_len(p, 8);
737 		break;
738 	case (LIST_column):
739 		/* FALLTHROUGH */
740 	case (LIST_tag):
741 		if (0 == width)
742 			width = term_len(p, 10);
743 		break;
744 	default:
745 		break;
746 	}
747 
748 	/*
749 	 * Whitespace control.  Inset bodies need an initial space,
750 	 * while diagonal bodies need two.
751 	 */
752 
753 	p->flags |= TERMP_NOSPACE;
754 
755 	switch (type) {
756 	case (LIST_diag):
757 		if (MDOC_BODY == n->type)
758 			term_word(p, "\\ \\ ");
759 		break;
760 	case (LIST_inset):
761 		if (MDOC_BODY == n->type)
762 			term_word(p, "\\ ");
763 		break;
764 	default:
765 		break;
766 	}
767 
768 	p->flags |= TERMP_NOSPACE;
769 
770 	switch (type) {
771 	case (LIST_diag):
772 		if (MDOC_HEAD == n->type)
773 			term_fontpush(p, TERMFONT_BOLD);
774 		break;
775 	default:
776 		break;
777 	}
778 
779 	/*
780 	 * Pad and break control.  This is the tricky part.  These flags
781 	 * are documented in term_flushln() in term.c.  Note that we're
782 	 * going to unset all of these flags in termp_it_post() when we
783 	 * exit.
784 	 */
785 
786 	switch (type) {
787 	case (LIST_bullet):
788 		/* FALLTHROUGH */
789 	case (LIST_dash):
790 		/* FALLTHROUGH */
791 	case (LIST_enum):
792 		/* FALLTHROUGH */
793 	case (LIST_hyphen):
794 		if (MDOC_HEAD == n->type)
795 			p->flags |= TERMP_NOBREAK;
796 		else
797 			p->flags |= TERMP_NOLPAD;
798 		break;
799 	case (LIST_hang):
800 		if (MDOC_HEAD == n->type)
801 			p->flags |= TERMP_NOBREAK;
802 		else
803 			p->flags |= TERMP_NOLPAD;
804 
805 		if (MDOC_HEAD != n->type)
806 			break;
807 
808 		/*
809 		 * This is ugly.  If `-hang' is specified and the body
810 		 * is a `Bl' or `Bd', then we want basically to nullify
811 		 * the "overstep" effect in term_flushln() and treat
812 		 * this as a `-ohang' list instead.
813 		 */
814 		if (n->next->child &&
815 				(MDOC_Bl == n->next->child->tok ||
816 				 MDOC_Bd == n->next->child->tok)) {
817 			p->flags &= ~TERMP_NOBREAK;
818 			p->flags &= ~TERMP_NOLPAD;
819 		} else
820 			p->flags |= TERMP_HANG;
821 		break;
822 	case (LIST_tag):
823 		if (MDOC_HEAD == n->type)
824 			p->flags |= TERMP_NOBREAK | TERMP_TWOSPACE;
825 		else
826 			p->flags |= TERMP_NOLPAD;
827 
828 		if (MDOC_HEAD != n->type)
829 			break;
830 		if (NULL == n->next || NULL == n->next->child)
831 			p->flags |= TERMP_DANGLE;
832 		break;
833 	case (LIST_column):
834 		if (MDOC_HEAD == n->type)
835 			break;
836 
837 		if (NULL == n->next)
838 			p->flags &= ~TERMP_NOBREAK;
839 		else
840 			p->flags |= TERMP_NOBREAK;
841 
842 		assert(n->prev);
843 		if (MDOC_BODY == n->prev->type)
844 			p->flags |= TERMP_NOLPAD;
845 
846 		break;
847 	case (LIST_diag):
848 		if (MDOC_HEAD == n->type)
849 			p->flags |= TERMP_NOBREAK;
850 		break;
851 	default:
852 		break;
853 	}
854 
855 	/*
856 	 * Margin control.  Set-head-width lists have their right
857 	 * margins shortened.  The body for these lists has the offset
858 	 * necessarily lengthened.  Everybody gets the offset.
859 	 */
860 
861 	p->offset += offset;
862 
863 	switch (type) {
864 	case (LIST_hang):
865 		/*
866 		 * Same stipulation as above, regarding `-hang'.  We
867 		 * don't want to recalculate rmargin and offsets when
868 		 * using `Bd' or `Bl' within `-hang' overstep lists.
869 		 */
870 		if (MDOC_HEAD == n->type && n->next->child &&
871 				(MDOC_Bl == n->next->child->tok ||
872 				 MDOC_Bd == n->next->child->tok))
873 			break;
874 		/* FALLTHROUGH */
875 	case (LIST_bullet):
876 		/* FALLTHROUGH */
877 	case (LIST_dash):
878 		/* FALLTHROUGH */
879 	case (LIST_enum):
880 		/* FALLTHROUGH */
881 	case (LIST_hyphen):
882 		/* FALLTHROUGH */
883 	case (LIST_tag):
884 		assert(width);
885 		if (MDOC_HEAD == n->type)
886 			p->rmargin = p->offset + width;
887 		else
888 			p->offset += width;
889 		break;
890 	case (LIST_column):
891 		assert(width);
892 		p->rmargin = p->offset + width;
893 		/*
894 		 * XXX - this behaviour is not documented: the
895 		 * right-most column is filled to the right margin.
896 		 */
897 		if (MDOC_HEAD == n->type)
898 			break;
899 		if (NULL == n->next && p->rmargin < p->maxrmargin)
900 			p->rmargin = p->maxrmargin;
901 		break;
902 	default:
903 		break;
904 	}
905 
906 	/*
907 	 * The dash, hyphen, bullet and enum lists all have a special
908 	 * HEAD character (temporarily bold, in some cases).
909 	 */
910 
911 	if (MDOC_HEAD == n->type)
912 		switch (type) {
913 		case (LIST_bullet):
914 			term_fontpush(p, TERMFONT_BOLD);
915 			term_word(p, "\\[bu]");
916 			term_fontpop(p);
917 			break;
918 		case (LIST_dash):
919 			/* FALLTHROUGH */
920 		case (LIST_hyphen):
921 			term_fontpush(p, TERMFONT_BOLD);
922 			term_word(p, "\\(hy");
923 			term_fontpop(p);
924 			break;
925 		case (LIST_enum):
926 			(pair->ppair->ppair->count)++;
927 			snprintf(buf, sizeof(buf), "%d.",
928 					pair->ppair->ppair->count);
929 			term_word(p, buf);
930 			break;
931 		default:
932 			break;
933 		}
934 
935 	/*
936 	 * If we're not going to process our children, indicate so here.
937 	 */
938 
939 	switch (type) {
940 	case (LIST_bullet):
941 		/* FALLTHROUGH */
942 	case (LIST_item):
943 		/* FALLTHROUGH */
944 	case (LIST_dash):
945 		/* FALLTHROUGH */
946 	case (LIST_hyphen):
947 		/* FALLTHROUGH */
948 	case (LIST_enum):
949 		if (MDOC_HEAD == n->type)
950 			return(0);
951 		break;
952 	case (LIST_column):
953 		if (MDOC_HEAD == n->type)
954 			return(0);
955 		break;
956 	default:
957 		break;
958 	}
959 
960 	return(1);
961 }
962 
963 
964 /* ARGSUSED */
965 static void
966 termp_it_post(DECL_ARGS)
967 {
968 	enum mdoc_list	   type;
969 
970 	if (MDOC_BLOCK == n->type)
971 		return;
972 
973 	type = n->parent->parent->parent->norm->Bl.type;
974 
975 	switch (type) {
976 	case (LIST_item):
977 		/* FALLTHROUGH */
978 	case (LIST_diag):
979 		/* FALLTHROUGH */
980 	case (LIST_inset):
981 		if (MDOC_BODY == n->type)
982 			term_newln(p);
983 		break;
984 	case (LIST_column):
985 		if (MDOC_BODY == n->type)
986 			term_flushln(p);
987 		break;
988 	default:
989 		term_newln(p);
990 		break;
991 	}
992 
993 	/*
994 	 * Now that our output is flushed, we can reset our tags.  Since
995 	 * only `It' sets these flags, we're free to assume that nobody
996 	 * has munged them in the meanwhile.
997 	 */
998 
999 	p->flags &= ~TERMP_DANGLE;
1000 	p->flags &= ~TERMP_NOBREAK;
1001 	p->flags &= ~TERMP_TWOSPACE;
1002 	p->flags &= ~TERMP_NOLPAD;
1003 	p->flags &= ~TERMP_HANG;
1004 }
1005 
1006 
1007 /* ARGSUSED */
1008 static int
1009 termp_nm_pre(DECL_ARGS)
1010 {
1011 
1012 	if (MDOC_BLOCK == n->type)
1013 		return(1);
1014 
1015 	if (MDOC_BODY == n->type) {
1016 		if (NULL == n->child)
1017 			return(0);
1018 		p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1019 		p->offset += term_len(p, 1) +
1020 		    (NULL == n->prev->child ? term_strlen(p, m->name) :
1021 		     MDOC_TEXT == n->prev->child->type ?
1022 			term_strlen(p, n->prev->child->string) :
1023 		     term_len(p, 5));
1024 		return(1);
1025 	}
1026 
1027 	if (NULL == n->child && NULL == m->name)
1028 		return(0);
1029 
1030 	if (MDOC_HEAD == n->type)
1031 		synopsis_pre(p, n->parent);
1032 
1033 	if (MDOC_HEAD == n->type && n->next->child) {
1034 		p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
1035 		p->rmargin = p->offset + term_len(p, 1);
1036 		if (NULL == n->child) {
1037 			p->rmargin += term_strlen(p, m->name);
1038 		} else if (MDOC_TEXT == n->child->type) {
1039 			p->rmargin += term_strlen(p, n->child->string);
1040 			if (n->child->next)
1041 				p->flags |= TERMP_HANG;
1042 		} else {
1043 			p->rmargin += term_len(p, 5);
1044 			p->flags |= TERMP_HANG;
1045 		}
1046 	}
1047 
1048 	term_fontpush(p, TERMFONT_BOLD);
1049 	if (NULL == n->child)
1050 		term_word(p, m->name);
1051 	return(1);
1052 }
1053 
1054 
1055 /* ARGSUSED */
1056 static void
1057 termp_nm_post(DECL_ARGS)
1058 {
1059 
1060 	if (MDOC_HEAD == n->type && n->next->child) {
1061 		term_flushln(p);
1062 		p->flags &= ~(TERMP_NOBREAK | TERMP_HANG);
1063 	} else if (MDOC_BODY == n->type && n->child) {
1064 		term_flushln(p);
1065 		p->flags &= ~TERMP_NOLPAD;
1066 	}
1067 }
1068 
1069 
1070 /* ARGSUSED */
1071 static int
1072 termp_fl_pre(DECL_ARGS)
1073 {
1074 
1075 	term_fontpush(p, TERMFONT_BOLD);
1076 	term_word(p, "\\-");
1077 
1078 	if (n->child)
1079 		p->flags |= TERMP_NOSPACE;
1080 	else if (n->next && n->next->line == n->line)
1081 		p->flags |= TERMP_NOSPACE;
1082 
1083 	return(1);
1084 }
1085 
1086 
1087 /* ARGSUSED */
1088 static int
1089 termp__a_pre(DECL_ARGS)
1090 {
1091 
1092 	if (n->prev && MDOC__A == n->prev->tok)
1093 		if (NULL == n->next || MDOC__A != n->next->tok)
1094 			term_word(p, "and");
1095 
1096 	return(1);
1097 }
1098 
1099 
1100 /* ARGSUSED */
1101 static int
1102 termp_an_pre(DECL_ARGS)
1103 {
1104 
1105 	if (NULL == n->child)
1106 		return(1);
1107 
1108 	/*
1109 	 * If not in the AUTHORS section, `An -split' will cause
1110 	 * newlines to occur before the author name.  If in the AUTHORS
1111 	 * section, by default, the first `An' invocation is nosplit,
1112 	 * then all subsequent ones, regardless of whether interspersed
1113 	 * with other macros/text, are split.  -split, in this case,
1114 	 * will override the condition of the implied first -nosplit.
1115 	 */
1116 
1117 	if (n->sec == SEC_AUTHORS) {
1118 		if ( ! (TERMP_ANPREC & p->flags)) {
1119 			if (TERMP_SPLIT & p->flags)
1120 				term_newln(p);
1121 			return(1);
1122 		}
1123 		if (TERMP_NOSPLIT & p->flags)
1124 			return(1);
1125 		term_newln(p);
1126 		return(1);
1127 	}
1128 
1129 	if (TERMP_SPLIT & p->flags)
1130 		term_newln(p);
1131 
1132 	return(1);
1133 }
1134 
1135 
1136 /* ARGSUSED */
1137 static void
1138 termp_an_post(DECL_ARGS)
1139 {
1140 
1141 	if (n->child) {
1142 		if (SEC_AUTHORS == n->sec)
1143 			p->flags |= TERMP_ANPREC;
1144 		return;
1145 	}
1146 
1147 	if (AUTH_split == n->norm->An.auth) {
1148 		p->flags &= ~TERMP_NOSPLIT;
1149 		p->flags |= TERMP_SPLIT;
1150 	} else if (AUTH_nosplit == n->norm->An.auth) {
1151 		p->flags &= ~TERMP_SPLIT;
1152 		p->flags |= TERMP_NOSPLIT;
1153 	}
1154 
1155 }
1156 
1157 
1158 /* ARGSUSED */
1159 static int
1160 termp_ns_pre(DECL_ARGS)
1161 {
1162 
1163 	if ( ! (MDOC_LINE & n->flags))
1164 		p->flags |= TERMP_NOSPACE;
1165 	return(1);
1166 }
1167 
1168 
1169 /* ARGSUSED */
1170 static int
1171 termp_rs_pre(DECL_ARGS)
1172 {
1173 
1174 	if (SEC_SEE_ALSO != n->sec)
1175 		return(1);
1176 	if (MDOC_BLOCK == n->type && n->prev)
1177 		term_vspace(p);
1178 	return(1);
1179 }
1180 
1181 
1182 /* ARGSUSED */
1183 static int
1184 termp_rv_pre(DECL_ARGS)
1185 {
1186 	int		 nchild;
1187 
1188 	term_newln(p);
1189 	term_word(p, "The");
1190 
1191 	nchild = n->nchild;
1192 	for (n = n->child; n; n = n->next) {
1193 		term_fontpush(p, TERMFONT_BOLD);
1194 		term_word(p, n->string);
1195 		term_fontpop(p);
1196 
1197 		p->flags |= TERMP_NOSPACE;
1198 		term_word(p, "()");
1199 
1200 		if (nchild > 2 && n->next) {
1201 			p->flags |= TERMP_NOSPACE;
1202 			term_word(p, ",");
1203 		}
1204 
1205 		if (n->next && NULL == n->next->next)
1206 			term_word(p, "and");
1207 	}
1208 
1209 	if (nchild > 1)
1210 		term_word(p, "functions return");
1211 	else
1212 		term_word(p, "function returns");
1213 
1214        	term_word(p, "the value 0 if successful; otherwise the value "
1215 			"-1 is returned and the global variable");
1216 
1217 	term_fontpush(p, TERMFONT_UNDER);
1218 	term_word(p, "errno");
1219 	term_fontpop(p);
1220 
1221        	term_word(p, "is set to indicate the error.");
1222 	p->flags |= TERMP_SENTENCE;
1223 
1224 	return(0);
1225 }
1226 
1227 
1228 /* ARGSUSED */
1229 static int
1230 termp_ex_pre(DECL_ARGS)
1231 {
1232 	int		 nchild;
1233 
1234 	term_newln(p);
1235 	term_word(p, "The");
1236 
1237 	nchild = n->nchild;
1238 	for (n = n->child; n; n = n->next) {
1239 		term_fontpush(p, TERMFONT_BOLD);
1240 		term_word(p, n->string);
1241 		term_fontpop(p);
1242 
1243 		if (nchild > 2 && n->next) {
1244 			p->flags |= TERMP_NOSPACE;
1245 			term_word(p, ",");
1246 		}
1247 
1248 		if (n->next && NULL == n->next->next)
1249 			term_word(p, "and");
1250 	}
1251 
1252 	if (nchild > 1)
1253 		term_word(p, "utilities exit");
1254 	else
1255 		term_word(p, "utility exits");
1256 
1257        	term_word(p, "0 on success, and >0 if an error occurs.");
1258 
1259 	p->flags |= TERMP_SENTENCE;
1260 	return(0);
1261 }
1262 
1263 
1264 /* ARGSUSED */
1265 static int
1266 termp_nd_pre(DECL_ARGS)
1267 {
1268 
1269 	if (MDOC_BODY != n->type)
1270 		return(1);
1271 
1272 #if defined(__OpenBSD__) || defined(__linux__)
1273 	term_word(p, "\\(en");
1274 #else
1275 	term_word(p, "\\(em");
1276 #endif
1277 	return(1);
1278 }
1279 
1280 
1281 /* ARGSUSED */
1282 static int
1283 termp_bl_pre(DECL_ARGS)
1284 {
1285 
1286 	return(MDOC_HEAD != n->type);
1287 }
1288 
1289 
1290 /* ARGSUSED */
1291 static void
1292 termp_bl_post(DECL_ARGS)
1293 {
1294 
1295 	if (MDOC_BLOCK == n->type)
1296 		term_newln(p);
1297 }
1298 
1299 /* ARGSUSED */
1300 static int
1301 termp_xr_pre(DECL_ARGS)
1302 {
1303 
1304 	if (NULL == (n = n->child))
1305 		return(0);
1306 
1307 	assert(MDOC_TEXT == n->type);
1308 	term_word(p, n->string);
1309 
1310 	if (NULL == (n = n->next))
1311 		return(0);
1312 
1313 	p->flags |= TERMP_NOSPACE;
1314 	term_word(p, "(");
1315 	p->flags |= TERMP_NOSPACE;
1316 
1317 	assert(MDOC_TEXT == n->type);
1318 	term_word(p, n->string);
1319 
1320 	p->flags |= TERMP_NOSPACE;
1321 	term_word(p, ")");
1322 
1323 	return(0);
1324 }
1325 
1326 /*
1327  * This decides how to assert whitespace before any of the SYNOPSIS set
1328  * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain
1329  * macro combos).
1330  */
1331 static void
1332 synopsis_pre(struct termp *p, const struct mdoc_node *n)
1333 {
1334 	/*
1335 	 * Obviously, if we're not in a SYNOPSIS or no prior macros
1336 	 * exist, do nothing.
1337 	 */
1338 	if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
1339 		return;
1340 
1341 	/*
1342 	 * If we're the second in a pair of like elements, emit our
1343 	 * newline and return.  UNLESS we're `Fo', `Fn', `Fn', in which
1344 	 * case we soldier on.
1345 	 */
1346 	if (n->prev->tok == n->tok &&
1347 			MDOC_Ft != n->tok &&
1348 			MDOC_Fo != n->tok &&
1349 			MDOC_Fn != n->tok) {
1350 		term_newln(p);
1351 		return;
1352 	}
1353 
1354 	/*
1355 	 * If we're one of the SYNOPSIS set and non-like pair-wise after
1356 	 * another (or Fn/Fo, which we've let slip through) then assert
1357 	 * vertical space, else only newline and move on.
1358 	 */
1359 	switch (n->prev->tok) {
1360 	case (MDOC_Fd):
1361 		/* FALLTHROUGH */
1362 	case (MDOC_Fn):
1363 		/* FALLTHROUGH */
1364 	case (MDOC_Fo):
1365 		/* FALLTHROUGH */
1366 	case (MDOC_In):
1367 		/* FALLTHROUGH */
1368 	case (MDOC_Vt):
1369 		term_vspace(p);
1370 		break;
1371 	case (MDOC_Ft):
1372 		if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
1373 			term_vspace(p);
1374 			break;
1375 		}
1376 		/* FALLTHROUGH */
1377 	default:
1378 		term_newln(p);
1379 		break;
1380 	}
1381 }
1382 
1383 
1384 static int
1385 termp_vt_pre(DECL_ARGS)
1386 {
1387 
1388 	if (MDOC_ELEM == n->type) {
1389 		synopsis_pre(p, n);
1390 		return(termp_under_pre(p, pair, m, n));
1391 	} else if (MDOC_BLOCK == n->type) {
1392 		synopsis_pre(p, n);
1393 		return(1);
1394 	} else if (MDOC_HEAD == n->type)
1395 		return(0);
1396 
1397 	return(termp_under_pre(p, pair, m, n));
1398 }
1399 
1400 
1401 /* ARGSUSED */
1402 static int
1403 termp_bold_pre(DECL_ARGS)
1404 {
1405 
1406 	term_fontpush(p, TERMFONT_BOLD);
1407 	return(1);
1408 }
1409 
1410 
1411 /* ARGSUSED */
1412 static int
1413 termp_fd_pre(DECL_ARGS)
1414 {
1415 
1416 	synopsis_pre(p, n);
1417 	return(termp_bold_pre(p, pair, m, n));
1418 }
1419 
1420 
1421 /* ARGSUSED */
1422 static int
1423 termp_sh_pre(DECL_ARGS)
1424 {
1425 
1426 	/* No vspace between consecutive `Sh' calls. */
1427 
1428 	switch (n->type) {
1429 	case (MDOC_BLOCK):
1430 		if (n->prev && MDOC_Sh == n->prev->tok)
1431 			if (NULL == n->prev->body->child)
1432 				break;
1433 		term_vspace(p);
1434 		break;
1435 	case (MDOC_HEAD):
1436 		term_fontpush(p, TERMFONT_BOLD);
1437 		break;
1438 	case (MDOC_BODY):
1439 		p->offset = term_len(p, INDENT);
1440 		break;
1441 	default:
1442 		break;
1443 	}
1444 	return(1);
1445 }
1446 
1447 
1448 /* ARGSUSED */
1449 static void
1450 termp_sh_post(DECL_ARGS)
1451 {
1452 
1453 	switch (n->type) {
1454 	case (MDOC_HEAD):
1455 		term_newln(p);
1456 		break;
1457 	case (MDOC_BODY):
1458 		term_newln(p);
1459 		p->offset = 0;
1460 		break;
1461 	default:
1462 		break;
1463 	}
1464 }
1465 
1466 
1467 /* ARGSUSED */
1468 static int
1469 termp_bt_pre(DECL_ARGS)
1470 {
1471 
1472 	term_word(p, "is currently in beta test.");
1473 	p->flags |= TERMP_SENTENCE;
1474 	return(0);
1475 }
1476 
1477 
1478 /* ARGSUSED */
1479 static void
1480 termp_lb_post(DECL_ARGS)
1481 {
1482 
1483 	if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags)
1484 		term_newln(p);
1485 }
1486 
1487 
1488 /* ARGSUSED */
1489 static int
1490 termp_ud_pre(DECL_ARGS)
1491 {
1492 
1493 	term_word(p, "currently under development.");
1494 	p->flags |= TERMP_SENTENCE;
1495 	return(0);
1496 }
1497 
1498 
1499 /* ARGSUSED */
1500 static int
1501 termp_d1_pre(DECL_ARGS)
1502 {
1503 
1504 	if (MDOC_BLOCK != n->type)
1505 		return(1);
1506 	term_newln(p);
1507 	p->offset += term_len(p, (INDENT + 1));
1508 	return(1);
1509 }
1510 
1511 
1512 /* ARGSUSED */
1513 static void
1514 termp_d1_post(DECL_ARGS)
1515 {
1516 
1517 	if (MDOC_BLOCK != n->type)
1518 		return;
1519 	term_newln(p);
1520 }
1521 
1522 
1523 /* ARGSUSED */
1524 static int
1525 termp_ft_pre(DECL_ARGS)
1526 {
1527 
1528 	/* NB: MDOC_LINE does not effect this! */
1529 	synopsis_pre(p, n);
1530 	term_fontpush(p, TERMFONT_UNDER);
1531 	return(1);
1532 }
1533 
1534 
1535 /* ARGSUSED */
1536 static int
1537 termp_fn_pre(DECL_ARGS)
1538 {
1539 	int		 pretty;
1540 
1541 	pretty = MDOC_SYNPRETTY & n->flags;
1542 
1543 	synopsis_pre(p, n);
1544 
1545 	if (NULL == (n = n->child))
1546 		return(0);
1547 
1548 	assert(MDOC_TEXT == n->type);
1549 	term_fontpush(p, TERMFONT_BOLD);
1550 	term_word(p, n->string);
1551 	term_fontpop(p);
1552 
1553 	p->flags |= TERMP_NOSPACE;
1554 	term_word(p, "(");
1555 	p->flags |= TERMP_NOSPACE;
1556 
1557 	for (n = n->next; n; n = n->next) {
1558 		assert(MDOC_TEXT == n->type);
1559 		term_fontpush(p, TERMFONT_UNDER);
1560 		term_word(p, n->string);
1561 		term_fontpop(p);
1562 
1563 		if (n->next) {
1564 			p->flags |= TERMP_NOSPACE;
1565 			term_word(p, ",");
1566 		}
1567 	}
1568 
1569 	p->flags |= TERMP_NOSPACE;
1570 	term_word(p, ")");
1571 
1572 	if (pretty) {
1573 		p->flags |= TERMP_NOSPACE;
1574 		term_word(p, ";");
1575 	}
1576 
1577 	return(0);
1578 }
1579 
1580 
1581 /* ARGSUSED */
1582 static int
1583 termp_fa_pre(DECL_ARGS)
1584 {
1585 	const struct mdoc_node	*nn;
1586 
1587 	if (n->parent->tok != MDOC_Fo) {
1588 		term_fontpush(p, TERMFONT_UNDER);
1589 		return(1);
1590 	}
1591 
1592 	for (nn = n->child; nn; nn = nn->next) {
1593 		term_fontpush(p, TERMFONT_UNDER);
1594 		term_word(p, nn->string);
1595 		term_fontpop(p);
1596 
1597 		if (nn->next) {
1598 			p->flags |= TERMP_NOSPACE;
1599 			term_word(p, ",");
1600 		}
1601 	}
1602 
1603 	if (n->child && n->next && n->next->tok == MDOC_Fa) {
1604 		p->flags |= TERMP_NOSPACE;
1605 		term_word(p, ",");
1606 	}
1607 
1608 	return(0);
1609 }
1610 
1611 
1612 /* ARGSUSED */
1613 static int
1614 termp_bd_pre(DECL_ARGS)
1615 {
1616 	size_t			 tabwidth, rm, rmax;
1617 	const struct mdoc_node	*nn;
1618 
1619 	if (MDOC_BLOCK == n->type) {
1620 		print_bvspace(p, n, n);
1621 		return(1);
1622 	} else if (MDOC_HEAD == n->type)
1623 		return(0);
1624 
1625 	if (n->norm->Bd.offs)
1626 		p->offset += a2offs(p, n->norm->Bd.offs);
1627 
1628 	/*
1629 	 * If -ragged or -filled are specified, the block does nothing
1630 	 * but change the indentation.  If -unfilled or -literal are
1631 	 * specified, text is printed exactly as entered in the display:
1632 	 * for macro lines, a newline is appended to the line.  Blank
1633 	 * lines are allowed.
1634 	 */
1635 
1636 	if (DISP_literal != n->norm->Bd.type &&
1637 			DISP_unfilled != n->norm->Bd.type)
1638 		return(1);
1639 
1640 	tabwidth = p->tabwidth;
1641 	if (DISP_literal == n->norm->Bd.type)
1642 		p->tabwidth = term_len(p, 8);
1643 
1644 	rm = p->rmargin;
1645 	rmax = p->maxrmargin;
1646 	p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
1647 
1648 	for (nn = n->child; nn; nn = nn->next) {
1649 		print_mdoc_node(p, pair, m, nn);
1650 		/*
1651 		 * If the printed node flushes its own line, then we
1652 		 * needn't do it here as well.  This is hacky, but the
1653 		 * notion of selective eoln whitespace is pretty dumb
1654 		 * anyway, so don't sweat it.
1655 		 */
1656 		switch (nn->tok) {
1657 		case (MDOC_Sm):
1658 			/* FALLTHROUGH */
1659 		case (MDOC_br):
1660 			/* FALLTHROUGH */
1661 		case (MDOC_sp):
1662 			/* FALLTHROUGH */
1663 		case (MDOC_Bl):
1664 			/* FALLTHROUGH */
1665 		case (MDOC_D1):
1666 			/* FALLTHROUGH */
1667 		case (MDOC_Dl):
1668 			/* FALLTHROUGH */
1669 		case (MDOC_Lp):
1670 			/* FALLTHROUGH */
1671 		case (MDOC_Pp):
1672 			continue;
1673 		default:
1674 			break;
1675 		}
1676 		if (nn->next && nn->next->line == nn->line)
1677 			continue;
1678 		term_flushln(p);
1679 		p->flags |= TERMP_NOSPACE;
1680 	}
1681 
1682 	p->tabwidth = tabwidth;
1683 	p->rmargin = rm;
1684 	p->maxrmargin = rmax;
1685 	return(0);
1686 }
1687 
1688 
1689 /* ARGSUSED */
1690 static void
1691 termp_bd_post(DECL_ARGS)
1692 {
1693 	size_t		 rm, rmax;
1694 
1695 	if (MDOC_BODY != n->type)
1696 		return;
1697 
1698 	rm = p->rmargin;
1699 	rmax = p->maxrmargin;
1700 
1701 	if (DISP_literal == n->norm->Bd.type ||
1702 			DISP_unfilled == n->norm->Bd.type)
1703 		p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
1704 
1705 	p->flags |= TERMP_NOSPACE;
1706 	term_newln(p);
1707 
1708 	p->rmargin = rm;
1709 	p->maxrmargin = rmax;
1710 }
1711 
1712 
1713 /* ARGSUSED */
1714 static int
1715 termp_bx_pre(DECL_ARGS)
1716 {
1717 
1718 	if (NULL != (n = n->child)) {
1719 		term_word(p, n->string);
1720 		p->flags |= TERMP_NOSPACE;
1721 		term_word(p, "BSD");
1722 	} else {
1723 		term_word(p, "BSD");
1724 		return(0);
1725 	}
1726 
1727 	if (NULL != (n = n->next)) {
1728 		p->flags |= TERMP_NOSPACE;
1729 		term_word(p, "-");
1730 		p->flags |= TERMP_NOSPACE;
1731 		term_word(p, n->string);
1732 	}
1733 
1734 	return(0);
1735 }
1736 
1737 
1738 /* ARGSUSED */
1739 static int
1740 termp_xx_pre(DECL_ARGS)
1741 {
1742 	const char	*pp;
1743 	int		 flags;
1744 
1745 	pp = NULL;
1746 	switch (n->tok) {
1747 	case (MDOC_Bsx):
1748 		pp = "BSD/OS";
1749 		break;
1750 	case (MDOC_Dx):
1751 		pp = "DragonFly";
1752 		break;
1753 	case (MDOC_Fx):
1754 		pp = "FreeBSD";
1755 		break;
1756 	case (MDOC_Nx):
1757 		pp = "NetBSD";
1758 		break;
1759 	case (MDOC_Ox):
1760 		pp = "OpenBSD";
1761 		break;
1762 	case (MDOC_Ux):
1763 		pp = "UNIX";
1764 		break;
1765 	default:
1766 		break;
1767 	}
1768 
1769 	term_word(p, pp);
1770 	if (n->child) {
1771 		flags = p->flags;
1772 		p->flags |= TERMP_KEEP;
1773 		term_word(p, n->child->string);
1774 		p->flags = flags;
1775 	}
1776 	return(0);
1777 }
1778 
1779 
1780 /* ARGSUSED */
1781 static int
1782 termp_igndelim_pre(DECL_ARGS)
1783 {
1784 
1785 	p->flags |= TERMP_IGNDELIM;
1786 	return(1);
1787 }
1788 
1789 
1790 /* ARGSUSED */
1791 static void
1792 termp_pf_post(DECL_ARGS)
1793 {
1794 
1795 	p->flags |= TERMP_NOSPACE;
1796 }
1797 
1798 
1799 /* ARGSUSED */
1800 static int
1801 termp_ss_pre(DECL_ARGS)
1802 {
1803 
1804 	switch (n->type) {
1805 	case (MDOC_BLOCK):
1806 		term_newln(p);
1807 		if (n->prev)
1808 			term_vspace(p);
1809 		break;
1810 	case (MDOC_HEAD):
1811 		term_fontpush(p, TERMFONT_BOLD);
1812 		p->offset = term_len(p, HALFINDENT);
1813 		break;
1814 	default:
1815 		break;
1816 	}
1817 
1818 	return(1);
1819 }
1820 
1821 
1822 /* ARGSUSED */
1823 static void
1824 termp_ss_post(DECL_ARGS)
1825 {
1826 
1827 	if (MDOC_HEAD == n->type)
1828 		term_newln(p);
1829 }
1830 
1831 
1832 /* ARGSUSED */
1833 static int
1834 termp_cd_pre(DECL_ARGS)
1835 {
1836 
1837 	synopsis_pre(p, n);
1838 	term_fontpush(p, TERMFONT_BOLD);
1839 	return(1);
1840 }
1841 
1842 
1843 /* ARGSUSED */
1844 static int
1845 termp_in_pre(DECL_ARGS)
1846 {
1847 
1848 	synopsis_pre(p, n);
1849 
1850 	if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) {
1851 		term_fontpush(p, TERMFONT_BOLD);
1852 		term_word(p, "#include");
1853 		term_word(p, "<");
1854 	} else {
1855 		term_word(p, "<");
1856 		term_fontpush(p, TERMFONT_UNDER);
1857 	}
1858 
1859 	p->flags |= TERMP_NOSPACE;
1860 	return(1);
1861 }
1862 
1863 
1864 /* ARGSUSED */
1865 static void
1866 termp_in_post(DECL_ARGS)
1867 {
1868 
1869 	if (MDOC_SYNPRETTY & n->flags)
1870 		term_fontpush(p, TERMFONT_BOLD);
1871 
1872 	p->flags |= TERMP_NOSPACE;
1873 	term_word(p, ">");
1874 
1875 	if (MDOC_SYNPRETTY & n->flags)
1876 		term_fontpop(p);
1877 }
1878 
1879 
1880 /* ARGSUSED */
1881 static int
1882 termp_sp_pre(DECL_ARGS)
1883 {
1884 	size_t		 i, len;
1885 
1886 	switch (n->tok) {
1887 	case (MDOC_sp):
1888 		len = n->child ? a2height(p, n->child->string) : 1;
1889 		break;
1890 	case (MDOC_br):
1891 		len = 0;
1892 		break;
1893 	default:
1894 		len = 1;
1895 		break;
1896 	}
1897 
1898 	if (0 == len)
1899 		term_newln(p);
1900 	for (i = 0; i < len; i++)
1901 		term_vspace(p);
1902 
1903 	return(0);
1904 }
1905 
1906 
1907 /* ARGSUSED */
1908 static int
1909 termp_quote_pre(DECL_ARGS)
1910 {
1911 
1912 	if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
1913 		return(1);
1914 
1915 	switch (n->tok) {
1916 	case (MDOC_Ao):
1917 		/* FALLTHROUGH */
1918 	case (MDOC_Aq):
1919 		term_word(p, "<");
1920 		break;
1921 	case (MDOC_Bro):
1922 		/* FALLTHROUGH */
1923 	case (MDOC_Brq):
1924 		term_word(p, "{");
1925 		break;
1926 	case (MDOC_Oo):
1927 		/* FALLTHROUGH */
1928 	case (MDOC_Op):
1929 		/* FALLTHROUGH */
1930 	case (MDOC_Bo):
1931 		/* FALLTHROUGH */
1932 	case (MDOC_Bq):
1933 		term_word(p, "[");
1934 		break;
1935 	case (MDOC_Do):
1936 		/* FALLTHROUGH */
1937 	case (MDOC_Dq):
1938 		term_word(p, "``");
1939 		break;
1940 	case (MDOC_Po):
1941 		/* FALLTHROUGH */
1942 	case (MDOC_Pq):
1943 		term_word(p, "(");
1944 		break;
1945 	case (MDOC__T):
1946 		/* FALLTHROUGH */
1947 	case (MDOC_Qo):
1948 		/* FALLTHROUGH */
1949 	case (MDOC_Qq):
1950 		term_word(p, "\"");
1951 		break;
1952 	case (MDOC_Ql):
1953 		/* FALLTHROUGH */
1954 	case (MDOC_So):
1955 		/* FALLTHROUGH */
1956 	case (MDOC_Sq):
1957 		term_word(p, "`");
1958 		break;
1959 	default:
1960 		abort();
1961 		/* NOTREACHED */
1962 	}
1963 
1964 	p->flags |= TERMP_NOSPACE;
1965 	return(1);
1966 }
1967 
1968 
1969 /* ARGSUSED */
1970 static void
1971 termp_quote_post(DECL_ARGS)
1972 {
1973 
1974 	if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
1975 		return;
1976 
1977 	p->flags |= TERMP_NOSPACE;
1978 
1979 	switch (n->tok) {
1980 	case (MDOC_Ao):
1981 		/* FALLTHROUGH */
1982 	case (MDOC_Aq):
1983 		term_word(p, ">");
1984 		break;
1985 	case (MDOC_Bro):
1986 		/* FALLTHROUGH */
1987 	case (MDOC_Brq):
1988 		term_word(p, "}");
1989 		break;
1990 	case (MDOC_Oo):
1991 		/* FALLTHROUGH */
1992 	case (MDOC_Op):
1993 		/* FALLTHROUGH */
1994 	case (MDOC_Bo):
1995 		/* FALLTHROUGH */
1996 	case (MDOC_Bq):
1997 		term_word(p, "]");
1998 		break;
1999 	case (MDOC_Do):
2000 		/* FALLTHROUGH */
2001 	case (MDOC_Dq):
2002 		term_word(p, "''");
2003 		break;
2004 	case (MDOC_Po):
2005 		/* FALLTHROUGH */
2006 	case (MDOC_Pq):
2007 		term_word(p, ")");
2008 		break;
2009 	case (MDOC__T):
2010 		/* FALLTHROUGH */
2011 	case (MDOC_Qo):
2012 		/* FALLTHROUGH */
2013 	case (MDOC_Qq):
2014 		term_word(p, "\"");
2015 		break;
2016 	case (MDOC_Ql):
2017 		/* FALLTHROUGH */
2018 	case (MDOC_So):
2019 		/* FALLTHROUGH */
2020 	case (MDOC_Sq):
2021 		term_word(p, "'");
2022 		break;
2023 	default:
2024 		abort();
2025 		/* NOTREACHED */
2026 	}
2027 }
2028 
2029 
2030 /* ARGSUSED */
2031 static int
2032 termp_fo_pre(DECL_ARGS)
2033 {
2034 
2035 	if (MDOC_BLOCK == n->type) {
2036 		synopsis_pre(p, n);
2037 		return(1);
2038 	} else if (MDOC_BODY == n->type) {
2039 		p->flags |= TERMP_NOSPACE;
2040 		term_word(p, "(");
2041 		p->flags |= TERMP_NOSPACE;
2042 		return(1);
2043 	}
2044 
2045 	if (NULL == n->child)
2046 		return(0);
2047 
2048 	/* XXX: we drop non-initial arguments as per groff. */
2049 
2050 	assert(n->child->string);
2051 	term_fontpush(p, TERMFONT_BOLD);
2052 	term_word(p, n->child->string);
2053 	return(0);
2054 }
2055 
2056 
2057 /* ARGSUSED */
2058 static void
2059 termp_fo_post(DECL_ARGS)
2060 {
2061 
2062 	if (MDOC_BODY != n->type)
2063 		return;
2064 
2065 	p->flags |= TERMP_NOSPACE;
2066 	term_word(p, ")");
2067 
2068 	if (MDOC_SYNPRETTY & n->flags) {
2069 		p->flags |= TERMP_NOSPACE;
2070 		term_word(p, ";");
2071 	}
2072 }
2073 
2074 
2075 /* ARGSUSED */
2076 static int
2077 termp_bf_pre(DECL_ARGS)
2078 {
2079 
2080 	if (MDOC_HEAD == n->type)
2081 		return(0);
2082 	else if (MDOC_BLOCK != n->type)
2083 		return(1);
2084 
2085 	if (FONT_Em == n->norm->Bf.font)
2086 		term_fontpush(p, TERMFONT_UNDER);
2087 	else if (FONT_Sy == n->norm->Bf.font)
2088 		term_fontpush(p, TERMFONT_BOLD);
2089 	else
2090 		term_fontpush(p, TERMFONT_NONE);
2091 
2092 	return(1);
2093 }
2094 
2095 
2096 /* ARGSUSED */
2097 static int
2098 termp_sm_pre(DECL_ARGS)
2099 {
2100 
2101 	assert(n->child && MDOC_TEXT == n->child->type);
2102 	if (0 == strcmp("on", n->child->string)) {
2103 		if (p->col)
2104 			p->flags &= ~TERMP_NOSPACE;
2105 		p->flags &= ~TERMP_NONOSPACE;
2106 	} else
2107 		p->flags |= TERMP_NONOSPACE;
2108 
2109 	return(0);
2110 }
2111 
2112 
2113 /* ARGSUSED */
2114 static int
2115 termp_ap_pre(DECL_ARGS)
2116 {
2117 
2118 	p->flags |= TERMP_NOSPACE;
2119 	term_word(p, "'");
2120 	p->flags |= TERMP_NOSPACE;
2121 	return(1);
2122 }
2123 
2124 
2125 /* ARGSUSED */
2126 static void
2127 termp____post(DECL_ARGS)
2128 {
2129 
2130 	/*
2131 	 * Handle lists of authors.  In general, print each followed by
2132 	 * a comma.  Don't print the comma if there are only two
2133 	 * authors.
2134 	 */
2135 	if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
2136 		if (NULL == n->next->next || MDOC__A != n->next->next->tok)
2137 			if (NULL == n->prev || MDOC__A != n->prev->tok)
2138 				return;
2139 
2140 	/* TODO: %U. */
2141 
2142 	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
2143 		return;
2144 
2145 	p->flags |= TERMP_NOSPACE;
2146 	if (NULL == n->next) {
2147 		term_word(p, ".");
2148 		p->flags |= TERMP_SENTENCE;
2149 	} else
2150 		term_word(p, ",");
2151 }
2152 
2153 
2154 /* ARGSUSED */
2155 static int
2156 termp_li_pre(DECL_ARGS)
2157 {
2158 
2159 	term_fontpush(p, TERMFONT_NONE);
2160 	return(1);
2161 }
2162 
2163 
2164 /* ARGSUSED */
2165 static int
2166 termp_lk_pre(DECL_ARGS)
2167 {
2168 	const struct mdoc_node *nn, *sv;
2169 
2170 	term_fontpush(p, TERMFONT_UNDER);
2171 
2172 	nn = sv = n->child;
2173 
2174 	if (NULL == nn || NULL == nn->next)
2175 		return(1);
2176 
2177 	for (nn = nn->next; nn; nn = nn->next)
2178 		term_word(p, nn->string);
2179 
2180 	term_fontpop(p);
2181 
2182 	p->flags |= TERMP_NOSPACE;
2183 	term_word(p, ":");
2184 
2185 	term_fontpush(p, TERMFONT_BOLD);
2186 	term_word(p, sv->string);
2187 	term_fontpop(p);
2188 
2189 	return(0);
2190 }
2191 
2192 
2193 /* ARGSUSED */
2194 static int
2195 termp_bk_pre(DECL_ARGS)
2196 {
2197 
2198 	switch (n->type) {
2199 	case (MDOC_BLOCK):
2200 		break;
2201 	case (MDOC_HEAD):
2202 		return(0);
2203 	case (MDOC_BODY):
2204 		if (n->parent->args || 0 == n->prev->nchild)
2205 			p->flags |= TERMP_PREKEEP;
2206 		break;
2207 	default:
2208 		abort();
2209 		/* NOTREACHED */
2210 	}
2211 
2212 	return(1);
2213 }
2214 
2215 
2216 /* ARGSUSED */
2217 static void
2218 termp_bk_post(DECL_ARGS)
2219 {
2220 
2221 	if (MDOC_BODY == n->type)
2222 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
2223 }
2224 
2225 /* ARGSUSED */
2226 static void
2227 termp__t_post(DECL_ARGS)
2228 {
2229 
2230 	/*
2231 	 * If we're in an `Rs' and there's a journal present, then quote
2232 	 * us instead of underlining us (for disambiguation).
2233 	 */
2234 	if (n->parent && MDOC_Rs == n->parent->tok &&
2235 			n->parent->norm->Rs.quote_T)
2236 		termp_quote_post(p, pair, m, n);
2237 
2238 	termp____post(p, pair, m, n);
2239 }
2240 
2241 /* ARGSUSED */
2242 static int
2243 termp__t_pre(DECL_ARGS)
2244 {
2245 
2246 	/*
2247 	 * If we're in an `Rs' and there's a journal present, then quote
2248 	 * us instead of underlining us (for disambiguation).
2249 	 */
2250 	if (n->parent && MDOC_Rs == n->parent->tok &&
2251 			n->parent->norm->Rs.quote_T)
2252 		return(termp_quote_pre(p, pair, m, n));
2253 
2254 	term_fontpush(p, TERMFONT_UNDER);
2255 	return(1);
2256 }
2257 
2258 /* ARGSUSED */
2259 static int
2260 termp_under_pre(DECL_ARGS)
2261 {
2262 
2263 	term_fontpush(p, TERMFONT_UNDER);
2264 	return(1);
2265 }
2266