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