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