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