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