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