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