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