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