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