xref: /freebsd/contrib/mandoc/mdoc_term.c (revision cb2887746f8b9dd4ad6b1e757cdc053a08b25a2e)
1 /* $Id: mdoc_term.c,v 1.387 2025/07/27 15:27:28 schwarze Exp $ */
2 /*
3  * Copyright (c) 2010,2012-2020,2022,2025 Ingo Schwarze <schwarze@openbsd.org>
4  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
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  * Plain text formatter for mdoc(7), used by mandoc(1)
20  * for ASCII, UTF-8, PostScript, and PDF output.
21  */
22 #include "config.h"
23 
24 #include <sys/types.h>
25 
26 #include <assert.h>
27 #include <ctype.h>
28 #include <limits.h>
29 #include <stdint.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 
34 #include "mandoc_aux.h"
35 #include "roff.h"
36 #include "mdoc.h"
37 #include "out.h"
38 #include "term.h"
39 #include "term_tag.h"
40 #include "main.h"
41 
42 struct	termpair {
43 	struct termpair	 *ppair;
44 	int		  count;
45 };
46 
47 #define	DECL_ARGS struct termp *p, \
48 		  struct termpair *pair, \
49 		  const struct roff_meta *meta, \
50 		  struct roff_node *n
51 
52 struct	mdoc_term_act {
53 	int	(*pre)(DECL_ARGS);
54 	void	(*post)(DECL_ARGS);
55 };
56 
57 static	int	  a2width(const struct termp *, const char *);
58 
59 static	void	  print_bvspace(struct termp *,
60 			struct roff_node *, struct roff_node *);
61 static	void	  print_mdoc_node(DECL_ARGS);
62 static	void	  print_mdoc_nodelist(DECL_ARGS);
63 static	void	  print_mdoc_head(struct termp *, const struct roff_meta *);
64 static	void	  print_mdoc_foot(struct termp *, const struct roff_meta *);
65 static	void	  synopsis_pre(struct termp *, struct roff_node *);
66 
67 static	void	  termp____post(DECL_ARGS);
68 static	void	  termp__t_post(DECL_ARGS);
69 static	void	  termp_bd_post(DECL_ARGS);
70 static	void	  termp_bk_post(DECL_ARGS);
71 static	void	  termp_bl_post(DECL_ARGS);
72 static	void	  termp_eo_post(DECL_ARGS);
73 static	void	  termp_fd_post(DECL_ARGS);
74 static	void	  termp_fo_post(DECL_ARGS);
75 static	void	  termp_in_post(DECL_ARGS);
76 static	void	  termp_it_post(DECL_ARGS);
77 static	void	  termp_lb_post(DECL_ARGS);
78 static	void	  termp_nm_post(DECL_ARGS);
79 static	void	  termp_pf_post(DECL_ARGS);
80 static	void	  termp_quote_post(DECL_ARGS);
81 static	void	  termp_sh_post(DECL_ARGS);
82 static	void	  termp_ss_post(DECL_ARGS);
83 static	void	  termp_xx_post(DECL_ARGS);
84 
85 static	int	  termp__a_pre(DECL_ARGS);
86 static	int	  termp__t_pre(DECL_ARGS);
87 static	int	  termp_abort_pre(DECL_ARGS);
88 static	int	  termp_an_pre(DECL_ARGS);
89 static	int	  termp_ap_pre(DECL_ARGS);
90 static	int	  termp_bd_pre(DECL_ARGS);
91 static	int	  termp_bf_pre(DECL_ARGS);
92 static	int	  termp_bk_pre(DECL_ARGS);
93 static	int	  termp_bl_pre(DECL_ARGS);
94 static	int	  termp_bold_pre(DECL_ARGS);
95 static	int	  termp_d1_pre(DECL_ARGS);
96 static	int	  termp_eo_pre(DECL_ARGS);
97 static	int	  termp_ex_pre(DECL_ARGS);
98 static	int	  termp_fa_pre(DECL_ARGS);
99 static	int	  termp_fd_pre(DECL_ARGS);
100 static	int	  termp_fl_pre(DECL_ARGS);
101 static	int	  termp_fn_pre(DECL_ARGS);
102 static	int	  termp_fo_pre(DECL_ARGS);
103 static	int	  termp_ft_pre(DECL_ARGS);
104 static	int	  termp_in_pre(DECL_ARGS);
105 static	int	  termp_it_pre(DECL_ARGS);
106 static	int	  termp_li_pre(DECL_ARGS);
107 static	int	  termp_lk_pre(DECL_ARGS);
108 static	int	  termp_nd_pre(DECL_ARGS);
109 static	int	  termp_nm_pre(DECL_ARGS);
110 static	int	  termp_ns_pre(DECL_ARGS);
111 static	int	  termp_quote_pre(DECL_ARGS);
112 static	int	  termp_rs_pre(DECL_ARGS);
113 static	int	  termp_sh_pre(DECL_ARGS);
114 static	int	  termp_skip_pre(DECL_ARGS);
115 static	int	  termp_sm_pre(DECL_ARGS);
116 static	int	  termp_pp_pre(DECL_ARGS);
117 static	int	  termp_ss_pre(DECL_ARGS);
118 static	int	  termp_under_pre(DECL_ARGS);
119 static	int	  termp_vt_pre(DECL_ARGS);
120 static	int	  termp_xr_pre(DECL_ARGS);
121 static	int	  termp_xx_pre(DECL_ARGS);
122 
123 static const struct mdoc_term_act mdoc_term_acts[MDOC_MAX - MDOC_Dd] = {
124 	{ NULL, NULL }, /* Dd */
125 	{ NULL, NULL }, /* Dt */
126 	{ NULL, NULL }, /* Os */
127 	{ termp_sh_pre, termp_sh_post }, /* Sh */
128 	{ termp_ss_pre, termp_ss_post }, /* Ss */
129 	{ termp_pp_pre, NULL }, /* Pp */
130 	{ termp_d1_pre, termp_bl_post }, /* D1 */
131 	{ termp_d1_pre, termp_bl_post }, /* Dl */
132 	{ termp_bd_pre, termp_bd_post }, /* Bd */
133 	{ NULL, NULL }, /* Ed */
134 	{ termp_bl_pre, termp_bl_post }, /* Bl */
135 	{ NULL, NULL }, /* El */
136 	{ termp_it_pre, termp_it_post }, /* It */
137 	{ termp_under_pre, NULL }, /* Ad */
138 	{ termp_an_pre, NULL }, /* An */
139 	{ termp_ap_pre, NULL }, /* Ap */
140 	{ termp_under_pre, NULL }, /* Ar */
141 	{ termp_fd_pre, NULL }, /* Cd */
142 	{ termp_bold_pre, NULL }, /* Cm */
143 	{ termp_li_pre, NULL }, /* Dv */
144 	{ NULL, NULL }, /* Er */
145 	{ NULL, NULL }, /* Ev */
146 	{ termp_ex_pre, NULL }, /* Ex */
147 	{ termp_fa_pre, NULL }, /* Fa */
148 	{ termp_fd_pre, termp_fd_post }, /* Fd */
149 	{ termp_fl_pre, NULL }, /* Fl */
150 	{ termp_fn_pre, NULL }, /* Fn */
151 	{ termp_ft_pre, NULL }, /* Ft */
152 	{ termp_bold_pre, NULL }, /* Ic */
153 	{ termp_in_pre, termp_in_post }, /* In */
154 	{ termp_li_pre, NULL }, /* Li */
155 	{ termp_nd_pre, NULL }, /* Nd */
156 	{ termp_nm_pre, termp_nm_post }, /* Nm */
157 	{ termp_quote_pre, termp_quote_post }, /* Op */
158 	{ termp_abort_pre, NULL }, /* Ot */
159 	{ termp_under_pre, NULL }, /* Pa */
160 	{ termp_ex_pre, NULL }, /* Rv */
161 	{ NULL, NULL }, /* St */
162 	{ termp_under_pre, NULL }, /* Va */
163 	{ termp_vt_pre, NULL }, /* Vt */
164 	{ termp_xr_pre, NULL }, /* Xr */
165 	{ termp__a_pre, termp____post }, /* %A */
166 	{ termp_under_pre, termp____post }, /* %B */
167 	{ NULL, termp____post }, /* %D */
168 	{ termp_under_pre, termp____post }, /* %I */
169 	{ termp_under_pre, termp____post }, /* %J */
170 	{ NULL, termp____post }, /* %N */
171 	{ NULL, termp____post }, /* %O */
172 	{ NULL, termp____post }, /* %P */
173 	{ NULL, termp____post }, /* %R */
174 	{ termp__t_pre, termp__t_post }, /* %T */
175 	{ NULL, termp____post }, /* %V */
176 	{ NULL, NULL }, /* Ac */
177 	{ termp_quote_pre, termp_quote_post }, /* Ao */
178 	{ termp_quote_pre, termp_quote_post }, /* Aq */
179 	{ NULL, NULL }, /* At */
180 	{ NULL, NULL }, /* Bc */
181 	{ termp_bf_pre, NULL }, /* Bf */
182 	{ termp_quote_pre, termp_quote_post }, /* Bo */
183 	{ termp_quote_pre, termp_quote_post }, /* Bq */
184 	{ termp_xx_pre, termp_xx_post }, /* Bsx */
185 	{ NULL, NULL }, /* Bx */
186 	{ termp_skip_pre, NULL }, /* Db */
187 	{ NULL, NULL }, /* Dc */
188 	{ termp_quote_pre, termp_quote_post }, /* Do */
189 	{ termp_quote_pre, termp_quote_post }, /* Dq */
190 	{ NULL, NULL }, /* Ec */ /* FIXME: no space */
191 	{ NULL, NULL }, /* Ef */
192 	{ termp_under_pre, NULL }, /* Em */
193 	{ termp_eo_pre, termp_eo_post }, /* Eo */
194 	{ termp_xx_pre, termp_xx_post }, /* Fx */
195 	{ termp_bold_pre, NULL }, /* Ms */
196 	{ termp_li_pre, NULL }, /* No */
197 	{ termp_ns_pre, NULL }, /* Ns */
198 	{ termp_xx_pre, termp_xx_post }, /* Nx */
199 	{ termp_xx_pre, termp_xx_post }, /* Ox */
200 	{ NULL, NULL }, /* Pc */
201 	{ NULL, termp_pf_post }, /* Pf */
202 	{ termp_quote_pre, termp_quote_post }, /* Po */
203 	{ termp_quote_pre, termp_quote_post }, /* Pq */
204 	{ NULL, NULL }, /* Qc */
205 	{ termp_quote_pre, termp_quote_post }, /* Ql */
206 	{ termp_quote_pre, termp_quote_post }, /* Qo */
207 	{ termp_quote_pre, termp_quote_post }, /* Qq */
208 	{ NULL, NULL }, /* Re */
209 	{ termp_rs_pre, NULL }, /* Rs */
210 	{ NULL, NULL }, /* Sc */
211 	{ termp_quote_pre, termp_quote_post }, /* So */
212 	{ termp_quote_pre, termp_quote_post }, /* Sq */
213 	{ termp_sm_pre, NULL }, /* Sm */
214 	{ termp_under_pre, NULL }, /* Sx */
215 	{ termp_bold_pre, NULL }, /* Sy */
216 	{ NULL, NULL }, /* Tn */
217 	{ termp_xx_pre, termp_xx_post }, /* Ux */
218 	{ NULL, NULL }, /* Xc */
219 	{ NULL, NULL }, /* Xo */
220 	{ termp_fo_pre, termp_fo_post }, /* Fo */
221 	{ NULL, NULL }, /* Fc */
222 	{ termp_quote_pre, termp_quote_post }, /* Oo */
223 	{ NULL, NULL }, /* Oc */
224 	{ termp_bk_pre, termp_bk_post }, /* Bk */
225 	{ NULL, NULL }, /* Ek */
226 	{ NULL, NULL }, /* Bt */
227 	{ NULL, NULL }, /* Hf */
228 	{ termp_under_pre, NULL }, /* Fr */
229 	{ NULL, NULL }, /* Ud */
230 	{ NULL, termp_lb_post }, /* Lb */
231 	{ termp_abort_pre, NULL }, /* Lp */
232 	{ termp_lk_pre, NULL }, /* Lk */
233 	{ termp_under_pre, NULL }, /* Mt */
234 	{ termp_quote_pre, termp_quote_post }, /* Brq */
235 	{ termp_quote_pre, termp_quote_post }, /* Bro */
236 	{ NULL, NULL }, /* Brc */
237 	{ NULL, termp____post }, /* %C */
238 	{ termp_skip_pre, NULL }, /* Es */
239 	{ termp_quote_pre, termp_quote_post }, /* En */
240 	{ termp_xx_pre, termp_xx_post }, /* Dx */
241 	{ NULL, termp____post }, /* %Q */
242 	{ NULL, termp____post }, /* %U */
243 	{ NULL, NULL }, /* Ta */
244 	{ termp_skip_pre, NULL }, /* Tg */
245 };
246 
247 
248 void
249 terminal_mdoc(void *arg, const struct roff_meta *mdoc)
250 {
251 	struct roff_node	*n, *nn;
252 	struct termp		*p;
253 
254 	p = (struct termp *)arg;
255 	p->tcol->rmargin = p->maxrmargin = p->defrmargin;
256 	term_tab_set(p, NULL);
257 	term_tab_set(p, "T");
258 	term_tab_set(p, ".5i");
259 
260 	n = mdoc->first->child;
261 	if (p->synopsisonly) {
262 		for (nn = NULL; n != NULL; n = n->next) {
263 			if (n->tok != MDOC_Sh)
264 				continue;
265 			if (n->sec == SEC_SYNOPSIS)
266 				break;
267 			if (nn == NULL && n->sec == SEC_NAME)
268 				nn = n;
269 		}
270 		if (n == NULL)
271 			n = nn;
272 		p->flags |= TERMP_NOSPACE;
273 		if (n != NULL && (n = n->child->next->child) != NULL)
274 			print_mdoc_nodelist(p, NULL, mdoc, n);
275 		term_newln(p);
276 	} else {
277 		term_begin(p, print_mdoc_head, print_mdoc_foot, mdoc);
278 		while (n != NULL &&
279 		    (n->type == ROFFT_COMMENT ||
280 		     n->flags & NODE_NOPRT))
281 			n = n->next;
282 		if (n != NULL) {
283 			if (n->tok != MDOC_Sh)
284 				term_vspace(p);
285 			print_mdoc_nodelist(p, NULL, mdoc, n);
286 		}
287 		term_end(p);
288 	}
289 }
290 
291 static void
292 print_mdoc_nodelist(DECL_ARGS)
293 {
294 	while (n != NULL) {
295 		print_mdoc_node(p, pair, meta, n);
296 		n = n->next;
297 	}
298 }
299 
300 static void
301 print_mdoc_node(DECL_ARGS)
302 {
303 	const struct mdoc_term_act *act;
304 	struct termpair	 npair;
305 	size_t		 offset, rmargin;  /* In basic units. */
306 	int		 chld;
307 
308 	/*
309 	 * In no-fill mode, break the output line at the beginning
310 	 * of new input lines except after \c, and nowhere else.
311 	 */
312 
313 	if (n->flags & NODE_NOFILL) {
314 		if (n->flags & NODE_LINE &&
315 		    (p->flags & TERMP_NONEWLINE) == 0)
316 			term_newln(p);
317 		p->flags |= TERMP_BRNEVER;
318 	} else {
319 		if (n->flags & NODE_LINE)
320 			term_tab_ref(p);
321 		p->flags &= ~TERMP_BRNEVER;
322 	}
323 
324 	if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
325 		return;
326 
327 	chld = 1;
328 	offset = p->tcol->offset;
329 	rmargin = p->tcol->rmargin;
330 	n->flags &= ~NODE_ENDED;
331 	n->prev_font = p->fonti;
332 
333 	memset(&npair, 0, sizeof(struct termpair));
334 	npair.ppair = pair;
335 
336 	if (n->flags & NODE_ID && n->tok != MDOC_Pp &&
337 	    (n->tok != MDOC_It || n->type != ROFFT_BLOCK))
338 		term_tag_write(n, p->line);
339 
340 	/*
341 	 * Keeps only work until the end of a line.  If a keep was
342 	 * invoked in a prior line, revert it to PREKEEP.
343 	 */
344 
345 	if (p->flags & TERMP_KEEP && n->flags & NODE_LINE) {
346 		p->flags &= ~TERMP_KEEP;
347 		p->flags |= TERMP_PREKEEP;
348 	}
349 
350 	/*
351 	 * After the keep flags have been set up, we may now
352 	 * produce output.  Note that some pre-handlers do so.
353 	 */
354 
355 	act = NULL;
356 	switch (n->type) {
357 	case ROFFT_TEXT:
358 		if (n->flags & NODE_LINE) {
359 			switch (*n->string) {
360 			case '\0':
361 				if (p->flags & TERMP_NONEWLINE)
362 					term_newln(p);
363 				else
364 					term_vspace(p);
365 				return;
366 			case ' ':
367 				if ((p->flags & TERMP_NONEWLINE) == 0)
368 					term_newln(p);
369 				break;
370 			default:
371 				break;
372 			}
373 		}
374 		if (NODE_DELIMC & n->flags)
375 			p->flags |= TERMP_NOSPACE;
376 		term_word(p, n->string);
377 		if (NODE_DELIMO & n->flags)
378 			p->flags |= TERMP_NOSPACE;
379 		break;
380 	case ROFFT_EQN:
381 		if ( ! (n->flags & NODE_LINE))
382 			p->flags |= TERMP_NOSPACE;
383 		term_eqn(p, n->eqn);
384 		if (n->next != NULL && ! (n->next->flags & NODE_LINE))
385 			p->flags |= TERMP_NOSPACE;
386 		break;
387 	case ROFFT_TBL:
388 		if (p->tbl.cols == NULL)
389 			term_newln(p);
390 		term_tbl(p, n->span);
391 		break;
392 	default:
393 		if (n->tok < ROFF_MAX) {
394 			roff_term_pre(p, n);
395 			return;
396 		}
397 		assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
398 		act = mdoc_term_acts + (n->tok - MDOC_Dd);
399 		if (act->pre != NULL &&
400 		    (n->end == ENDBODY_NOT || n->child != NULL))
401 			chld = (*act->pre)(p, &npair, meta, n);
402 		break;
403 	}
404 
405 	if (chld && n->child)
406 		print_mdoc_nodelist(p, &npair, meta, n->child);
407 
408 	term_fontpopq(p,
409 	    (ENDBODY_NOT == n->end ? n : n->body)->prev_font);
410 
411 	switch (n->type) {
412 	case ROFFT_TEXT:
413 		break;
414 	case ROFFT_TBL:
415 		break;
416 	case ROFFT_EQN:
417 		break;
418 	default:
419 		if (act->post == NULL || n->flags & NODE_ENDED)
420 			break;
421 		(void)(*act->post)(p, &npair, meta, n);
422 
423 		/*
424 		 * Explicit end tokens not only call the post
425 		 * handler, but also tell the respective block
426 		 * that it must not call the post handler again.
427 		 */
428 		if (ENDBODY_NOT != n->end)
429 			n->body->flags |= NODE_ENDED;
430 		break;
431 	}
432 
433 	if (NODE_EOS & n->flags)
434 		p->flags |= TERMP_SENTENCE;
435 
436 	if (n->type != ROFFT_TEXT)
437 		p->tcol->offset = offset;
438 	p->tcol->rmargin = rmargin;
439 }
440 
441 static void
442 print_mdoc_foot(struct termp *p, const struct roff_meta *meta)
443 {
444 	char	*title;
445 	size_t	 datelen, titlen;  /* In basic units. */
446 
447 	assert(meta->title != NULL);
448 	datelen = term_strlen(p, meta->date);
449 	if (meta->msec == NULL)
450 		title = mandoc_strdup(meta->title);
451 	else
452 		mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec);
453 	titlen = term_strlen(p, title);
454 
455 	term_fontrepl(p, TERMFONT_NONE);
456 	term_vspace(p);
457 
458 	/* Bottom left corner: operating system. */
459 
460 	p->tcol->offset = 0;
461 	p->tcol->rmargin = p->maxrmargin > datelen ?
462 	    (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0;
463 	p->trailspace = 1;
464 	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
465 
466 	term_word(p, meta->os);
467 	term_flushln(p);
468 
469 	/* At the bottom in the middle: manual date. */
470 
471 	p->tcol->offset = p->tcol->rmargin;
472 	p->tcol->rmargin = p->maxrmargin > titlen ?
473 	    p->maxrmargin - titlen : 0;
474 	p->flags |= TERMP_NOSPACE;
475 
476 	term_word(p, meta->date);
477 	term_flushln(p);
478 
479 	/* Bottom right corner: manual title and section. */
480 
481 	p->tcol->offset = p->tcol->rmargin;
482 	p->tcol->rmargin = p->maxrmargin;
483 	p->trailspace = 0;
484 	p->flags &= ~TERMP_NOBREAK;
485 	p->flags |= TERMP_NOSPACE;
486 
487 	term_word(p, title);
488 	term_flushln(p);
489 
490 	p->tcol->offset = 0;
491 	p->flags = 0;
492 	free(title);
493 }
494 
495 static void
496 print_mdoc_head(struct termp *p, const struct roff_meta *meta)
497 {
498 	char			*volume, *title;
499 	size_t			 vollen, titlen;  /* In basic units. */
500 
501 	assert(meta->vol);
502 	if (NULL == meta->arch)
503 		volume = mandoc_strdup(meta->vol);
504 	else
505 		mandoc_asprintf(&volume, "%s (%s)",
506 		    meta->vol, meta->arch);
507 	vollen = term_strlen(p, volume);
508 
509 	/* Top left corner: manual title and section. */
510 
511 	if (NULL == meta->msec)
512 		title = mandoc_strdup(meta->title);
513 	else
514 		mandoc_asprintf(&title, "%s(%s)",
515 		    meta->title, meta->msec);
516 	titlen = term_strlen(p, title);
517 
518 	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
519 	p->trailspace = 1;
520 	p->tcol->offset = 0;
521 	p->tcol->rmargin =
522 	    titlen * 2 + term_len(p, 2) + vollen < p->maxrmargin ?
523 	    (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
524 	    vollen < p->maxrmargin ? p->maxrmargin - vollen : 0;
525 
526 	term_word(p, title);
527 	term_flushln(p);
528 
529 	/* At the top in the middle: manual volume. */
530 
531 	p->flags |= TERMP_NOSPACE;
532 	p->tcol->offset = p->tcol->rmargin;
533 	p->tcol->rmargin = p->tcol->offset + vollen + titlen <
534 	    p->maxrmargin ? p->maxrmargin - titlen : p->maxrmargin;
535 
536 	term_word(p, volume);
537 	term_flushln(p);
538 
539 	/* Top right corner: title and section, again. */
540 
541 	p->flags &= ~TERMP_NOBREAK;
542 	p->trailspace = 0;
543 	if (p->tcol->rmargin + titlen <= p->maxrmargin) {
544 		p->flags |= TERMP_NOSPACE;
545 		p->tcol->offset = p->tcol->rmargin;
546 		p->tcol->rmargin = p->maxrmargin;
547 		term_word(p, title);
548 		term_flushln(p);
549 	}
550 
551 	p->flags &= ~TERMP_NOSPACE;
552 	p->tcol->offset = 0;
553 	p->tcol->rmargin = p->maxrmargin;
554 	free(title);
555 	free(volume);
556 }
557 
558 /*
559  * Interpret the string v as a scaled width or, if the syntax is invalid,
560  * measure how much width it takes up when printed.  In both cases,
561  * return the width in basic units.
562  */
563 static int
564 a2width(const struct termp *p, const char *v)
565 {
566 	struct roffsu	 su;
567 	const char	*end;
568 
569 	end = a2roffsu(v, &su, SCALE_MAX);
570 	if (end == NULL || *end != '\0') {
571 		su.unit = SCALE_BU;
572 		su.scale = term_strlen(p, v);
573 	}
574 	return term_hspan(p, &su);
575 }
576 
577 /*
578  * Determine how much space to print out before block elements of `It'
579  * (and thus `Bl') and `Bd'.  And then go ahead and print that space,
580  * too.
581  */
582 static void
583 print_bvspace(struct termp *p, struct roff_node *bl, struct roff_node *n)
584 {
585 	struct roff_node *nn;
586 
587 	term_newln(p);
588 
589 	if ((bl->tok == MDOC_Bd && bl->norm->Bd.comp) ||
590 	    (bl->tok == MDOC_Bl && bl->norm->Bl.comp))
591 		return;
592 
593 	/* Do not vspace directly after Ss/Sh. */
594 
595 	nn = n;
596 	while (roff_node_prev(nn) == NULL) {
597 		do {
598 			nn = nn->parent;
599 			if (nn->type == ROFFT_ROOT)
600 				return;
601 		} while (nn->type != ROFFT_BLOCK);
602 		if (nn->tok == MDOC_Sh || nn->tok == MDOC_Ss)
603 			return;
604 		if (nn->tok == MDOC_It &&
605 		    nn->parent->parent->norm->Bl.type != LIST_item)
606 			break;
607 	}
608 
609 	/*
610 	 * No vertical space after:
611 	 * items in .Bl -column
612 	 * items without a body in .Bl -diag
613 	 */
614 
615 	if (bl->tok != MDOC_Bl ||
616 	    n->prev == NULL || n->prev->tok != MDOC_It ||
617 	    (bl->norm->Bl.type != LIST_column &&
618 	     (bl->norm->Bl.type != LIST_diag ||
619 	      n->prev->body->child != NULL)))
620 		term_vspace(p);
621 }
622 
623 
624 static int
625 termp_it_pre(DECL_ARGS)
626 {
627 	struct roffsu		su;
628 	char			buf[24];
629 	const struct roff_node *bl, *nn;
630 	size_t			ncols;	/* Number of columns in .Bl -column. */
631 	size_t			dcol;	/* Column spacing in basic units. */
632 	int			i;	/* Zero-based column index. */
633 	int			offset;	/* Start of column in basic units. */
634 	int			width;	/* Column width in basic units. */
635 	enum mdoc_list		type;
636 
637 	if (n->type == ROFFT_BLOCK) {
638 		print_bvspace(p, n->parent->parent, n);
639 		if (n->flags & NODE_ID)
640 			term_tag_write(n, p->line);
641 		return 1;
642 	}
643 
644 	bl = n->parent->parent->parent;
645 	type = bl->norm->Bl.type;
646 
647 	/*
648 	 * Defaults for specific list types.
649 	 */
650 
651 	switch (type) {
652 	case LIST_bullet:
653 	case LIST_dash:
654 	case LIST_hyphen:
655 	case LIST_enum:
656 		width = term_len(p, 2);
657 		break;
658 	case LIST_hang:
659 	case LIST_tag:
660 		width = term_len(p, 8);
661 		break;
662 	case LIST_column:
663 		width = term_len(p, 10);
664 		break;
665 	default:
666 		width = 0;
667 		break;
668 	}
669 	offset = 0;
670 
671 	/*
672 	 * First calculate width and offset.  This is pretty easy unless
673 	 * we're a -column list, in which case all prior columns must
674 	 * be accounted for.
675 	 */
676 
677 	if (bl->norm->Bl.offs != NULL) {
678 		offset = a2width(p, bl->norm->Bl.offs);
679 		if (offset < 0 && (size_t)(-offset) > p->tcol->offset)
680 			offset = -p->tcol->offset;
681 		else if (offset > SHRT_MAX)
682 			offset = 0;
683 	}
684 
685 	switch (type) {
686 	case LIST_column:
687 		if (n->type == ROFFT_HEAD)
688 			break;
689 
690 		/*
691 		 * Imitate groff's column handling:
692 		 * - For each earlier column, add its width.
693 		 * - For less than 5 columns, add four more blanks per
694 		 *   column.
695 		 * - For exactly 5 columns, add three more blank per
696 		 *   column.
697 		 * - For more than 5 columns, add only one column.
698 		 */
699 		ncols = bl->norm->Bl.ncols;
700 		dcol = ncols < 5 ? term_len(p, 4) :
701 		    ncols == 5 ? term_len(p, 3) : term_len(p, 1);
702 
703 		/*
704 		 * Calculate the offset by applying all prior ROFFT_BODY,
705 		 * so we stop at the ROFFT_HEAD (nn->prev == NULL).
706 		 */
707 
708 		for (i = 0, nn = n->prev;
709 		    nn->prev && i < (int)ncols;
710 		    nn = nn->prev, i++) {
711 			su.unit = SCALE_BU;
712 			su.scale = term_strlen(p, bl->norm->Bl.cols[i]);
713 			offset += term_hspan(p, &su) + dcol;
714 		}
715 
716 		/*
717 		 * When exceeding the declared number of columns, leave
718 		 * the remaining widths at 0.  This will later be
719 		 * adjusted to the default width of 10, or, for the last
720 		 * column, stretched to the right margin.
721 		 */
722 		if (i >= (int)ncols)
723 			break;
724 
725 		/*
726 		 * Use the declared column widths, extended as explained
727 		 * in the preceding paragraph.
728 		 */
729 		su.unit = SCALE_BU;
730 		su.scale = term_strlen(p, bl->norm->Bl.cols[i]);
731 		width = term_hspan(p, &su) + dcol;
732 		break;
733 	default:
734 		if (NULL == bl->norm->Bl.width)
735 			break;
736 
737 		/*
738 		 * Note: buffer the width by 2, which is groff's magic
739 		 * number for buffering single arguments.  See the above
740 		 * handling for column for how this changes.
741 		 */
742 		width = a2width(p, bl->norm->Bl.width) + term_len(p, 2);
743 		if (width < 0 && (size_t)(-width) > p->tcol->offset)
744 			width = -p->tcol->offset;
745 		else if (width > SHRT_MAX)
746 			width = 0;
747 		break;
748 	}
749 
750 	/*
751 	 * Whitespace control.  Inset bodies need an initial space,
752 	 * while diagonal bodies need two.
753 	 */
754 
755 	p->flags |= TERMP_NOSPACE;
756 
757 	switch (type) {
758 	case LIST_diag:
759 		if (n->type == ROFFT_BODY)
760 			term_word(p, "\\ \\ ");
761 		break;
762 	case LIST_inset:
763 		if (n->type == ROFFT_BODY && n->parent->head->child != NULL)
764 			term_word(p, "\\ ");
765 		break;
766 	default:
767 		break;
768 	}
769 
770 	p->flags |= TERMP_NOSPACE;
771 
772 	switch (type) {
773 	case LIST_diag:
774 		if (n->type == ROFFT_HEAD)
775 			term_fontpush(p, TERMFONT_BOLD);
776 		break;
777 	default:
778 		break;
779 	}
780 
781 	/*
782 	 * Pad and break control.  This is the tricky part.  These flags
783 	 * are documented in term_flushln() in term.c.  Note that we're
784 	 * going to unset all of these flags in termp_it_post() when we
785 	 * exit.
786 	 */
787 
788 	switch (type) {
789 	case LIST_enum:
790 	case LIST_bullet:
791 	case LIST_dash:
792 	case LIST_hyphen:
793 		if (n->type == ROFFT_HEAD) {
794 			p->flags |= TERMP_NOBREAK | TERMP_HANG;
795 			p->trailspace = 1;
796 		} else if (width <= (int)term_len(p, 2))
797 			p->flags |= TERMP_NOPAD;
798 		break;
799 	case LIST_hang:
800 		if (n->type != ROFFT_HEAD)
801 			break;
802 		p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
803 		p->trailspace = 1;
804 		break;
805 	case LIST_tag:
806 		if (n->type != ROFFT_HEAD)
807 			break;
808 
809 		p->flags |= TERMP_NOBREAK | TERMP_BRTRSP | TERMP_BRIND;
810 		p->trailspace = 2;
811 
812 		if (NULL == n->next || NULL == n->next->child)
813 			p->flags |= TERMP_HANG;
814 		break;
815 	case LIST_column:
816 		if (n->type == ROFFT_HEAD)
817 			break;
818 
819 		if (NULL == n->next) {
820 			p->flags &= ~TERMP_NOBREAK;
821 			p->trailspace = 0;
822 		} else {
823 			p->flags |= TERMP_NOBREAK;
824 			p->trailspace = 1;
825 		}
826 
827 		break;
828 	case LIST_diag:
829 		if (n->type != ROFFT_HEAD)
830 			break;
831 		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
832 		p->trailspace = 1;
833 		break;
834 	default:
835 		break;
836 	}
837 
838 	/*
839 	 * Margin control.  Set-head-width lists have their right
840 	 * margins shortened.  The body for these lists has the offset
841 	 * necessarily lengthened.  Everybody gets the offset.
842 	 */
843 
844 	p->tcol->offset += offset;
845 
846 	switch (type) {
847 	case LIST_bullet:
848 	case LIST_dash:
849 	case LIST_enum:
850 	case LIST_hyphen:
851 	case LIST_hang:
852 	case LIST_tag:
853 		if (n->type == ROFFT_HEAD)
854 			p->tcol->rmargin = p->tcol->offset + width;
855 		else
856 			p->tcol->offset += width;
857 		break;
858 	case LIST_column:
859 		assert(width);
860 		p->tcol->rmargin = p->tcol->offset + width;
861 		/*
862 		 * XXX - this behaviour is not documented: the
863 		 * right-most column is filled to the right margin.
864 		 */
865 		if (n->type == ROFFT_HEAD)
866 			break;
867 		if (n->next == NULL && p->tcol->rmargin < p->maxrmargin)
868 			p->tcol->rmargin = p->maxrmargin;
869 		break;
870 	default:
871 		break;
872 	}
873 
874 	/*
875 	 * The dash, hyphen, bullet and enum lists all have a special
876 	 * HEAD character (temporarily bold, in some cases).
877 	 */
878 
879 	if (n->type == ROFFT_HEAD)
880 		switch (type) {
881 		case LIST_bullet:
882 			term_fontpush(p, TERMFONT_BOLD);
883 			term_word(p, "\\[bu]");
884 			term_fontpop(p);
885 			break;
886 		case LIST_dash:
887 		case LIST_hyphen:
888 			term_fontpush(p, TERMFONT_BOLD);
889 			term_word(p, "-");
890 			term_fontpop(p);
891 			break;
892 		case LIST_enum:
893 			(pair->ppair->ppair->count)++;
894 			(void)snprintf(buf, sizeof(buf), "%d.",
895 			    pair->ppair->ppair->count);
896 			term_word(p, buf);
897 			break;
898 		default:
899 			break;
900 		}
901 
902 	/*
903 	 * If we're not going to process our children, indicate so here.
904 	 */
905 
906 	switch (type) {
907 	case LIST_bullet:
908 	case LIST_item:
909 	case LIST_dash:
910 	case LIST_hyphen:
911 	case LIST_enum:
912 		if (n->type == ROFFT_HEAD)
913 			return 0;
914 		break;
915 	case LIST_column:
916 		if (n->type == ROFFT_HEAD)
917 			return 0;
918 		p->minbl = 0;
919 		break;
920 	default:
921 		break;
922 	}
923 
924 	return 1;
925 }
926 
927 static void
928 termp_it_post(DECL_ARGS)
929 {
930 	enum mdoc_list	   type;
931 
932 	if (n->type == ROFFT_BLOCK)
933 		return;
934 
935 	type = n->parent->parent->parent->norm->Bl.type;
936 
937 	switch (type) {
938 	case LIST_item:
939 	case LIST_diag:
940 	case LIST_inset:
941 		if (n->type == ROFFT_BODY)
942 			term_newln(p);
943 		break;
944 	case LIST_column:
945 		if (n->type == ROFFT_BODY)
946 			term_flushln(p);
947 		break;
948 	default:
949 		term_newln(p);
950 		break;
951 	}
952 
953 	/*
954 	 * Now that our output is flushed, we can reset our tags.  Since
955 	 * only `It' sets these flags, we're free to assume that nobody
956 	 * has munged them in the meanwhile.
957 	 */
958 
959 	p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP | TERMP_BRIND | TERMP_HANG);
960 	p->trailspace = 0;
961 }
962 
963 static int
964 termp_nm_pre(DECL_ARGS)
965 {
966 	const char	*cp;
967 
968 	if (n->type == ROFFT_BLOCK) {
969 		p->flags |= TERMP_PREKEEP;
970 		return 1;
971 	}
972 
973 	if (n->type == ROFFT_BODY) {
974 		if (n->child == NULL)
975 			return 0;
976 		p->flags |= TERMP_NOSPACE;
977 		cp = NULL;
978 		if (n->prev->child != NULL)
979 		    cp = n->prev->child->string;
980 		if (cp == NULL)
981 			cp = meta->name;
982 		if (cp == NULL)
983 			p->tcol->offset += term_len(p, 6);
984 		else
985 			p->tcol->offset += term_len(p, 1) +
986 			    term_strlen(p, cp);
987 		return 1;
988 	}
989 
990 	if (n->child == NULL)
991 		return 0;
992 
993 	if (n->type == ROFFT_HEAD)
994 		synopsis_pre(p, n->parent);
995 
996 	if (n->type == ROFFT_HEAD &&
997 	    n->next != NULL && n->next->child != NULL) {
998 		p->flags |= TERMP_NOSPACE | TERMP_NOBREAK | TERMP_BRIND;
999 		p->trailspace = 1;
1000 		p->tcol->rmargin = p->tcol->offset + term_len(p, 1);
1001 		if (n->child == NULL)
1002 			p->tcol->rmargin += term_strlen(p, meta->name);
1003 		else if (n->child->type == ROFFT_TEXT) {
1004 			p->tcol->rmargin += term_strlen(p, n->child->string);
1005 			if (n->child->next != NULL)
1006 				p->flags |= TERMP_HANG;
1007 		} else {
1008 			p->tcol->rmargin += term_len(p, 5);
1009 			p->flags |= TERMP_HANG;
1010 		}
1011 	}
1012 	return termp_bold_pre(p, pair, meta, n);
1013 }
1014 
1015 static void
1016 termp_nm_post(DECL_ARGS)
1017 {
1018 	switch (n->type) {
1019 	case ROFFT_BLOCK:
1020 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
1021 		break;
1022 	case ROFFT_HEAD:
1023 		if (n->next == NULL || n->next->child == NULL)
1024 			break;
1025 		term_flushln(p);
1026 		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
1027 		p->trailspace = 0;
1028 		break;
1029 	case ROFFT_BODY:
1030 		if (n->child != NULL)
1031 			term_flushln(p);
1032 		break;
1033 	default:
1034 		break;
1035 	}
1036 }
1037 
1038 static int
1039 termp_fl_pre(DECL_ARGS)
1040 {
1041 	struct roff_node *nn;
1042 
1043 	term_fontpush(p, TERMFONT_BOLD);
1044 	term_word(p, "\\-");
1045 
1046 	if (n->child != NULL ||
1047 	    ((nn = roff_node_next(n)) != NULL &&
1048 	     nn->type != ROFFT_TEXT &&
1049 	     (nn->flags & NODE_LINE) == 0))
1050 		p->flags |= TERMP_NOSPACE;
1051 
1052 	return 1;
1053 }
1054 
1055 static int
1056 termp__a_pre(DECL_ARGS)
1057 {
1058 	struct roff_node *nn;
1059 
1060 	if ((nn = roff_node_prev(n)) != NULL && nn->tok == MDOC__A &&
1061 	    ((nn = roff_node_next(n)) == NULL || nn->tok != MDOC__A))
1062 		term_word(p, "and");
1063 
1064 	return 1;
1065 }
1066 
1067 static int
1068 termp_an_pre(DECL_ARGS)
1069 {
1070 
1071 	if (n->norm->An.auth == AUTH_split) {
1072 		p->flags &= ~TERMP_NOSPLIT;
1073 		p->flags |= TERMP_SPLIT;
1074 		return 0;
1075 	}
1076 	if (n->norm->An.auth == AUTH_nosplit) {
1077 		p->flags &= ~TERMP_SPLIT;
1078 		p->flags |= TERMP_NOSPLIT;
1079 		return 0;
1080 	}
1081 
1082 	if (p->flags & TERMP_SPLIT)
1083 		term_newln(p);
1084 
1085 	if (n->sec == SEC_AUTHORS && ! (p->flags & TERMP_NOSPLIT))
1086 		p->flags |= TERMP_SPLIT;
1087 
1088 	return 1;
1089 }
1090 
1091 static int
1092 termp_ns_pre(DECL_ARGS)
1093 {
1094 
1095 	if ( ! (NODE_LINE & n->flags))
1096 		p->flags |= TERMP_NOSPACE;
1097 	return 1;
1098 }
1099 
1100 static int
1101 termp_rs_pre(DECL_ARGS)
1102 {
1103 	if (SEC_SEE_ALSO != n->sec)
1104 		return 1;
1105 	if (n->type == ROFFT_BLOCK && roff_node_prev(n) != NULL)
1106 		term_vspace(p);
1107 	return 1;
1108 }
1109 
1110 static int
1111 termp_ex_pre(DECL_ARGS)
1112 {
1113 	term_newln(p);
1114 	return 1;
1115 }
1116 
1117 static int
1118 termp_nd_pre(DECL_ARGS)
1119 {
1120 	if (n->type == ROFFT_BODY)
1121 		term_word(p, "\\(en");
1122 	return 1;
1123 }
1124 
1125 static int
1126 termp_bl_pre(DECL_ARGS)
1127 {
1128 	switch (n->type) {
1129 	case ROFFT_BLOCK:
1130 		term_newln(p);
1131 		return 1;
1132 	case ROFFT_HEAD:
1133 		return 0;
1134 	default:
1135 		return 1;
1136 	}
1137 }
1138 
1139 static void
1140 termp_bl_post(DECL_ARGS)
1141 {
1142 	if (n->type != ROFFT_BLOCK)
1143 		return;
1144 	term_newln(p);
1145 	if (n->tok != MDOC_Bl || n->norm->Bl.type != LIST_column)
1146 		return;
1147 	term_tab_set(p, NULL);
1148 	term_tab_set(p, "T");
1149 	term_tab_set(p, ".5i");
1150 }
1151 
1152 static int
1153 termp_xr_pre(DECL_ARGS)
1154 {
1155 	if (NULL == (n = n->child))
1156 		return 0;
1157 
1158 	assert(n->type == ROFFT_TEXT);
1159 	term_word(p, n->string);
1160 
1161 	if (NULL == (n = n->next))
1162 		return 0;
1163 
1164 	p->flags |= TERMP_NOSPACE;
1165 	term_word(p, "(");
1166 	p->flags |= TERMP_NOSPACE;
1167 
1168 	assert(n->type == ROFFT_TEXT);
1169 	term_word(p, n->string);
1170 
1171 	p->flags |= TERMP_NOSPACE;
1172 	term_word(p, ")");
1173 
1174 	return 0;
1175 }
1176 
1177 /*
1178  * This decides how to assert whitespace before any of the SYNOPSIS set
1179  * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain
1180  * macro combos).
1181  */
1182 static void
1183 synopsis_pre(struct termp *p, struct roff_node *n)
1184 {
1185 	struct roff_node	*np;
1186 
1187 	if ((n->flags & NODE_SYNPRETTY) == 0 ||
1188 	    (np = roff_node_prev(n)) == NULL)
1189 		return;
1190 
1191 	/*
1192 	 * If we're the second in a pair of like elements, emit our
1193 	 * newline and return.  UNLESS we're `Fo', `Fn', `Fn', in which
1194 	 * case we soldier on.
1195 	 */
1196 	if (np->tok == n->tok &&
1197 	    MDOC_Ft != n->tok &&
1198 	    MDOC_Fo != n->tok &&
1199 	    MDOC_Fn != n->tok) {
1200 		term_newln(p);
1201 		return;
1202 	}
1203 
1204 	/*
1205 	 * If we're one of the SYNOPSIS set and non-like pair-wise after
1206 	 * another (or Fn/Fo, which we've let slip through) then assert
1207 	 * vertical space, else only newline and move on.
1208 	 */
1209 	switch (np->tok) {
1210 	case MDOC_Fd:
1211 	case MDOC_Fn:
1212 	case MDOC_Fo:
1213 	case MDOC_In:
1214 	case MDOC_Vt:
1215 		term_vspace(p);
1216 		break;
1217 	case MDOC_Ft:
1218 		if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) {
1219 			term_vspace(p);
1220 			break;
1221 		}
1222 		/* FALLTHROUGH */
1223 	default:
1224 		term_newln(p);
1225 		break;
1226 	}
1227 }
1228 
1229 static int
1230 termp_vt_pre(DECL_ARGS)
1231 {
1232 	switch (n->type) {
1233 	case ROFFT_ELEM:
1234 		return termp_ft_pre(p, pair, meta, n);
1235 	case ROFFT_BLOCK:
1236 		synopsis_pre(p, n);
1237 		return 1;
1238 	case ROFFT_HEAD:
1239 		return 0;
1240 	default:
1241 		return termp_under_pre(p, pair, meta, n);
1242 	}
1243 }
1244 
1245 static int
1246 termp_bold_pre(DECL_ARGS)
1247 {
1248 	term_fontpush(p, TERMFONT_BOLD);
1249 	return 1;
1250 }
1251 
1252 static int
1253 termp_fd_pre(DECL_ARGS)
1254 {
1255 	synopsis_pre(p, n);
1256 	return termp_bold_pre(p, pair, meta, n);
1257 }
1258 
1259 static void
1260 termp_fd_post(DECL_ARGS)
1261 {
1262 	term_newln(p);
1263 }
1264 
1265 static int
1266 termp_sh_pre(DECL_ARGS)
1267 {
1268 	struct roff_node	*np;
1269 
1270 	switch (n->type) {
1271 	case ROFFT_BLOCK:
1272 		/*
1273 		 * Vertical space before sections, except
1274 		 * when the previous section was empty.
1275 		 */
1276 		if ((np = roff_node_prev(n)) == NULL ||
1277 		    np->tok != MDOC_Sh ||
1278 		    (np->body != NULL && np->body->child != NULL))
1279 			term_vspace(p);
1280 		break;
1281 	case ROFFT_HEAD:
1282 		p->fontibi = 1;
1283 		return termp_bold_pre(p, pair, meta, n);
1284 	case ROFFT_BODY:
1285 		p->tcol->offset = term_len(p, p->defindent);
1286 		term_tab_set(p, NULL);
1287 		term_tab_set(p, "T");
1288 		term_tab_set(p, ".5i");
1289 		if (n->sec == SEC_AUTHORS)
1290 			p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT);
1291 		break;
1292 	default:
1293 		break;
1294 	}
1295 	return 1;
1296 }
1297 
1298 static void
1299 termp_sh_post(DECL_ARGS)
1300 {
1301 	switch (n->type) {
1302 	case ROFFT_HEAD:
1303 		p->fontibi = 0;
1304 		term_newln(p);
1305 		break;
1306 	case ROFFT_BODY:
1307 		term_newln(p);
1308 		p->tcol->offset = 0;
1309 		break;
1310 	default:
1311 		break;
1312 	}
1313 }
1314 
1315 static void
1316 termp_lb_post(DECL_ARGS)
1317 {
1318 	if (n->sec == SEC_LIBRARY && n->flags & NODE_LINE)
1319 		term_newln(p);
1320 }
1321 
1322 static int
1323 termp_d1_pre(DECL_ARGS)
1324 {
1325 	if (n->type != ROFFT_BLOCK)
1326 		return 1;
1327 	term_newln(p);
1328 	p->tcol->offset += term_len(p, p->defindent + 1);
1329 	term_tab_set(p, NULL);
1330 	term_tab_set(p, "T");
1331 	term_tab_set(p, ".5i");
1332 	return 1;
1333 }
1334 
1335 static int
1336 termp_ft_pre(DECL_ARGS)
1337 {
1338 	synopsis_pre(p, n);
1339 	return termp_under_pre(p, pair, meta, n);
1340 }
1341 
1342 static int
1343 termp_fn_pre(DECL_ARGS)
1344 {
1345 	size_t		 rmargin = 0;
1346 	int		 pretty;
1347 
1348 	synopsis_pre(p, n);
1349 	pretty = n->flags & NODE_SYNPRETTY;
1350 	if ((n = n->child) == NULL)
1351 		return 0;
1352 
1353 	if (pretty) {
1354 		rmargin = p->tcol->rmargin;
1355 		p->tcol->rmargin = p->tcol->offset + term_len(p, 4);
1356 		p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
1357 	}
1358 
1359 	assert(n->type == ROFFT_TEXT);
1360 	term_fontpush(p, TERMFONT_BOLD);
1361 	term_word(p, n->string);
1362 	term_fontpop(p);
1363 
1364 	if (pretty) {
1365 		term_flushln(p);
1366 		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
1367 		p->flags |= TERMP_NOPAD;
1368 		p->tcol->offset = p->tcol->rmargin;
1369 		p->tcol->rmargin = rmargin;
1370 	}
1371 
1372 	p->flags |= TERMP_NOSPACE;
1373 	term_word(p, "(");
1374 	p->flags |= TERMP_NOSPACE;
1375 
1376 	for (n = n->next; n; n = n->next) {
1377 		assert(n->type == ROFFT_TEXT);
1378 		term_fontpush(p, TERMFONT_UNDER);
1379 		if (pretty)
1380 			p->flags |= TERMP_NBRWORD;
1381 		term_word(p, n->string);
1382 		term_fontpop(p);
1383 
1384 		if (n->next) {
1385 			p->flags |= TERMP_NOSPACE;
1386 			term_word(p, ",");
1387 		}
1388 	}
1389 
1390 	p->flags |= TERMP_NOSPACE;
1391 	term_word(p, ")");
1392 
1393 	if (pretty) {
1394 		p->flags |= TERMP_NOSPACE;
1395 		term_word(p, ";");
1396 		term_flushln(p);
1397 	}
1398 	return 0;
1399 }
1400 
1401 static int
1402 termp_fa_pre(DECL_ARGS)
1403 {
1404 	const struct roff_node	*nn;
1405 
1406 	if (n->parent->tok != MDOC_Fo)
1407 		return termp_under_pre(p, pair, meta, n);
1408 
1409 	for (nn = n->child; nn != NULL; nn = nn->next) {
1410 		term_fontpush(p, TERMFONT_UNDER);
1411 		p->flags |= TERMP_NBRWORD;
1412 		term_word(p, nn->string);
1413 		term_fontpop(p);
1414 		if (nn->next != NULL) {
1415 			p->flags |= TERMP_NOSPACE;
1416 			term_word(p, ",");
1417 		}
1418 	}
1419 	if (n->child != NULL &&
1420 	    (nn = roff_node_next(n)) != NULL &&
1421 	    nn->tok == MDOC_Fa) {
1422 		p->flags |= TERMP_NOSPACE;
1423 		term_word(p, ",");
1424 	}
1425 	return 0;
1426 }
1427 
1428 static int
1429 termp_bd_pre(DECL_ARGS)
1430 {
1431 	int			 offset;  /* In basic units. */
1432 
1433 	if (n->type == ROFFT_BLOCK) {
1434 		print_bvspace(p, n, n);
1435 		return 1;
1436 	} else if (n->type == ROFFT_HEAD)
1437 		return 0;
1438 
1439 	/* Handle the -offset argument. */
1440 
1441 	if (n->norm->Bd.offs == NULL ||
1442 	    ! strcmp(n->norm->Bd.offs, "left"))
1443 		/* nothing */;
1444 	else if ( ! strcmp(n->norm->Bd.offs, "indent"))
1445 		p->tcol->offset += term_len(p, p->defindent + 1);
1446 	else if ( ! strcmp(n->norm->Bd.offs, "indent-two"))
1447 		p->tcol->offset += term_len(p, (p->defindent + 1) * 2);
1448 	else {
1449 		offset = a2width(p, n->norm->Bd.offs);
1450 		if (offset < 0 && (size_t)(-offset) > p->tcol->offset)
1451 			p->tcol->offset = 0;
1452 		else if (offset < SHRT_MAX)
1453 			p->tcol->offset += offset;
1454 	}
1455 
1456 	switch (n->norm->Bd.type) {
1457 	case DISP_literal:
1458 		term_tab_set(p, NULL);
1459 		term_tab_set(p, "T");
1460 		term_tab_set(p, "8n");
1461 		break;
1462 	case DISP_centered:
1463 		p->flags |= TERMP_CENTER;
1464 		break;
1465 	default:
1466 		break;
1467 	}
1468 	return 1;
1469 }
1470 
1471 static void
1472 termp_bd_post(DECL_ARGS)
1473 {
1474 	if (n->type != ROFFT_BODY)
1475 		return;
1476 	if (n->norm->Bd.type == DISP_unfilled ||
1477 	    n->norm->Bd.type == DISP_literal)
1478 		p->flags |= TERMP_BRNEVER;
1479 	p->flags |= TERMP_NOSPACE;
1480 	term_newln(p);
1481 	p->flags &= ~TERMP_BRNEVER;
1482 	if (n->norm->Bd.type == DISP_centered)
1483 		p->flags &= ~TERMP_CENTER;
1484 }
1485 
1486 static int
1487 termp_xx_pre(DECL_ARGS)
1488 {
1489 	if ((n->aux = p->flags & TERMP_PREKEEP) == 0)
1490 		p->flags |= TERMP_PREKEEP;
1491 	return 1;
1492 }
1493 
1494 static void
1495 termp_xx_post(DECL_ARGS)
1496 {
1497 	if (n->aux == 0)
1498 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
1499 }
1500 
1501 static void
1502 termp_pf_post(DECL_ARGS)
1503 {
1504 	if (n->next != NULL && (n->next->flags & NODE_LINE) == 0)
1505 		p->flags |= TERMP_NOSPACE;
1506 }
1507 
1508 static int
1509 termp_ss_pre(DECL_ARGS)
1510 {
1511 	switch (n->type) {
1512 	case ROFFT_BLOCK:
1513 		if (roff_node_prev(n) == NULL)
1514 			term_newln(p);
1515 		else
1516 			term_vspace(p);
1517 		break;
1518 	case ROFFT_HEAD:
1519 		p->tcol->offset = term_len(p, p->defindent) / 2 + 1;
1520 		p->fontibi = 1;
1521 		return termp_bold_pre(p, pair, meta, n);
1522 	case ROFFT_BODY:
1523 		p->tcol->offset = term_len(p, p->defindent);
1524 		term_tab_set(p, NULL);
1525 		term_tab_set(p, "T");
1526 		term_tab_set(p, ".5i");
1527 		break;
1528 	default:
1529 		break;
1530 	}
1531 	return 1;
1532 }
1533 
1534 static void
1535 termp_ss_post(DECL_ARGS)
1536 {
1537 	switch (n->type) {
1538 	case ROFFT_HEAD:
1539 		p->fontibi = 0;
1540 		/* FALLTHROUGH */
1541 	case ROFFT_BODY:
1542 		term_newln(p);
1543 		break;
1544 	default:
1545 		break;
1546 	}
1547 }
1548 
1549 static int
1550 termp_in_pre(DECL_ARGS)
1551 {
1552 	synopsis_pre(p, n);
1553 	if (n->flags & NODE_SYNPRETTY && n->flags & NODE_LINE) {
1554 		term_fontpush(p, TERMFONT_BOLD);
1555 		term_word(p, "#include");
1556 		term_word(p, "<");
1557 	} else {
1558 		term_word(p, "<");
1559 		term_fontpush(p, TERMFONT_UNDER);
1560 	}
1561 	p->flags |= TERMP_NOSPACE;
1562 	return 1;
1563 }
1564 
1565 static void
1566 termp_in_post(DECL_ARGS)
1567 {
1568 	if (n->flags & NODE_SYNPRETTY)
1569 		term_fontpush(p, TERMFONT_BOLD);
1570 	p->flags |= TERMP_NOSPACE;
1571 	term_word(p, ">");
1572 	if (n->flags & NODE_SYNPRETTY)
1573 		term_fontpop(p);
1574 }
1575 
1576 static int
1577 termp_pp_pre(DECL_ARGS)
1578 {
1579 	term_vspace(p);
1580 	if (n->flags & NODE_ID)
1581 		term_tag_write(n, p->line);
1582 	return 0;
1583 }
1584 
1585 static int
1586 termp_skip_pre(DECL_ARGS)
1587 {
1588 	return 0;
1589 }
1590 
1591 static int
1592 termp_quote_pre(DECL_ARGS)
1593 {
1594 	if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
1595 		return 1;
1596 
1597 	switch (n->tok) {
1598 	case MDOC_Ao:
1599 	case MDOC_Aq:
1600 		term_word(p, n->child != NULL && n->child->next == NULL &&
1601 		    n->child->tok == MDOC_Mt ? "<" : "\\(la");
1602 		break;
1603 	case MDOC_Bro:
1604 	case MDOC_Brq:
1605 		term_word(p, "{");
1606 		break;
1607 	case MDOC_Oo:
1608 	case MDOC_Op:
1609 	case MDOC_Bo:
1610 	case MDOC_Bq:
1611 		term_word(p, "[");
1612 		break;
1613 	case MDOC__T:
1614 		/* FALLTHROUGH */
1615 	case MDOC_Do:
1616 	case MDOC_Dq:
1617 		term_word(p, "\\(lq");
1618 		break;
1619 	case MDOC_En:
1620 		if (NULL == n->norm->Es ||
1621 		    NULL == n->norm->Es->child)
1622 			return 1;
1623 		term_word(p, n->norm->Es->child->string);
1624 		break;
1625 	case MDOC_Po:
1626 	case MDOC_Pq:
1627 		term_word(p, "(");
1628 		break;
1629 	case MDOC_Qo:
1630 	case MDOC_Qq:
1631 		term_word(p, "\"");
1632 		break;
1633 	case MDOC_Ql:
1634 	case MDOC_So:
1635 	case MDOC_Sq:
1636 		term_word(p, "\\(oq");
1637 		break;
1638 	default:
1639 		abort();
1640 	}
1641 
1642 	p->flags |= TERMP_NOSPACE;
1643 	return 1;
1644 }
1645 
1646 static void
1647 termp_quote_post(DECL_ARGS)
1648 {
1649 
1650 	if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
1651 		return;
1652 
1653 	p->flags |= TERMP_NOSPACE;
1654 
1655 	switch (n->tok) {
1656 	case MDOC_Ao:
1657 	case MDOC_Aq:
1658 		term_word(p, n->child != NULL && n->child->next == NULL &&
1659 		    n->child->tok == MDOC_Mt ? ">" : "\\(ra");
1660 		break;
1661 	case MDOC_Bro:
1662 	case MDOC_Brq:
1663 		term_word(p, "}");
1664 		break;
1665 	case MDOC_Oo:
1666 	case MDOC_Op:
1667 	case MDOC_Bo:
1668 	case MDOC_Bq:
1669 		term_word(p, "]");
1670 		break;
1671 	case MDOC__T:
1672 		/* FALLTHROUGH */
1673 	case MDOC_Do:
1674 	case MDOC_Dq:
1675 		term_word(p, "\\(rq");
1676 		break;
1677 	case MDOC_En:
1678 		if (n->norm->Es == NULL ||
1679 		    n->norm->Es->child == NULL ||
1680 		    n->norm->Es->child->next == NULL)
1681 			p->flags &= ~TERMP_NOSPACE;
1682 		else
1683 			term_word(p, n->norm->Es->child->next->string);
1684 		break;
1685 	case MDOC_Po:
1686 	case MDOC_Pq:
1687 		term_word(p, ")");
1688 		break;
1689 	case MDOC_Qo:
1690 	case MDOC_Qq:
1691 		term_word(p, "\"");
1692 		break;
1693 	case MDOC_Ql:
1694 	case MDOC_So:
1695 	case MDOC_Sq:
1696 		term_word(p, "\\(cq");
1697 		break;
1698 	default:
1699 		abort();
1700 	}
1701 }
1702 
1703 static int
1704 termp_eo_pre(DECL_ARGS)
1705 {
1706 
1707 	if (n->type != ROFFT_BODY)
1708 		return 1;
1709 
1710 	if (n->end == ENDBODY_NOT &&
1711 	    n->parent->head->child == NULL &&
1712 	    n->child != NULL &&
1713 	    n->child->end != ENDBODY_NOT)
1714 		term_word(p, "\\&");
1715 	else if (n->end != ENDBODY_NOT ? n->child != NULL :
1716 	     n->parent->head->child != NULL && (n->child != NULL ||
1717 	     (n->parent->tail != NULL && n->parent->tail->child != NULL)))
1718 		p->flags |= TERMP_NOSPACE;
1719 
1720 	return 1;
1721 }
1722 
1723 static void
1724 termp_eo_post(DECL_ARGS)
1725 {
1726 	int	 body, tail;
1727 
1728 	if (n->type != ROFFT_BODY)
1729 		return;
1730 
1731 	if (n->end != ENDBODY_NOT) {
1732 		p->flags &= ~TERMP_NOSPACE;
1733 		return;
1734 	}
1735 
1736 	body = n->child != NULL || n->parent->head->child != NULL;
1737 	tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
1738 
1739 	if (body && tail)
1740 		p->flags |= TERMP_NOSPACE;
1741 	else if ( ! (body || tail))
1742 		term_word(p, "\\&");
1743 	else if ( ! tail)
1744 		p->flags &= ~TERMP_NOSPACE;
1745 }
1746 
1747 static int
1748 termp_fo_pre(DECL_ARGS)
1749 {
1750 	size_t rmargin;
1751 
1752 	switch (n->type) {
1753 	case ROFFT_BLOCK:
1754 		synopsis_pre(p, n);
1755 		return 1;
1756 	case ROFFT_BODY:
1757 		rmargin = p->tcol->rmargin;
1758 		if (n->flags & NODE_SYNPRETTY) {
1759 			p->tcol->rmargin = p->tcol->offset + term_len(p, 4);
1760 			p->flags |= TERMP_NOBREAK | TERMP_BRIND |
1761 					TERMP_HANG;
1762 		}
1763 		p->flags |= TERMP_NOSPACE;
1764 		term_word(p, "(");
1765 		p->flags |= TERMP_NOSPACE;
1766 		if (n->flags & NODE_SYNPRETTY) {
1767 			term_flushln(p);
1768 			p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND |
1769 					TERMP_HANG);
1770 			p->flags |= TERMP_NOPAD;
1771 			p->tcol->offset = p->tcol->rmargin;
1772 			p->tcol->rmargin = rmargin;
1773 		}
1774 		return 1;
1775 	default:
1776 		return termp_bold_pre(p, pair, meta, n);
1777 	}
1778 }
1779 
1780 static void
1781 termp_fo_post(DECL_ARGS)
1782 {
1783 	if (n->type != ROFFT_BODY)
1784 		return;
1785 
1786 	p->flags |= TERMP_NOSPACE;
1787 	term_word(p, ")");
1788 
1789 	if (n->flags & NODE_SYNPRETTY) {
1790 		p->flags |= TERMP_NOSPACE;
1791 		term_word(p, ";");
1792 		term_flushln(p);
1793 	}
1794 }
1795 
1796 static int
1797 termp_bf_pre(DECL_ARGS)
1798 {
1799 	switch (n->type) {
1800 	case ROFFT_HEAD:
1801 		return 0;
1802 	case ROFFT_BODY:
1803 		break;
1804 	default:
1805 		return 1;
1806 	}
1807 	switch (n->norm->Bf.font) {
1808 	case FONT_Em:
1809 		return termp_under_pre(p, pair, meta, n);
1810 	case FONT_Sy:
1811 		return termp_bold_pre(p, pair, meta, n);
1812 	default:
1813 		return termp_li_pre(p, pair, meta, n);
1814 	}
1815 }
1816 
1817 static int
1818 termp_sm_pre(DECL_ARGS)
1819 {
1820 	if (n->child == NULL)
1821 		p->flags ^= TERMP_NONOSPACE;
1822 	else if (strcmp(n->child->string, "on") == 0)
1823 		p->flags &= ~TERMP_NONOSPACE;
1824 	else
1825 		p->flags |= TERMP_NONOSPACE;
1826 
1827 	if (p->col && ! (TERMP_NONOSPACE & p->flags))
1828 		p->flags &= ~TERMP_NOSPACE;
1829 
1830 	return 0;
1831 }
1832 
1833 static int
1834 termp_ap_pre(DECL_ARGS)
1835 {
1836 	p->flags |= TERMP_NOSPACE;
1837 	term_word(p, "'");
1838 	p->flags |= TERMP_NOSPACE;
1839 	return 1;
1840 }
1841 
1842 static void
1843 termp____post(DECL_ARGS)
1844 {
1845 	struct roff_node *nn;
1846 
1847 	/*
1848 	 * Handle lists of authors.  In general, print each followed by
1849 	 * a comma.  Don't print the comma if there are only two
1850 	 * authors.
1851 	 */
1852 	if (n->tok == MDOC__A &&
1853 	    (nn = roff_node_next(n)) != NULL && nn->tok == MDOC__A &&
1854 	    ((nn = roff_node_next(nn)) == NULL || nn->tok != MDOC__A) &&
1855 	    ((nn = roff_node_prev(n)) == NULL || nn->tok != MDOC__A))
1856 		return;
1857 
1858 	/* TODO: %U. */
1859 
1860 	if (n->parent == NULL || n->parent->tok != MDOC_Rs)
1861 		return;
1862 
1863 	p->flags |= TERMP_NOSPACE;
1864 	if (roff_node_next(n) == NULL) {
1865 		term_word(p, ".");
1866 		p->flags |= TERMP_SENTENCE;
1867 	} else
1868 		term_word(p, ",");
1869 }
1870 
1871 static int
1872 termp_li_pre(DECL_ARGS)
1873 {
1874 	term_fontpush(p, TERMFONT_NONE);
1875 	return 1;
1876 }
1877 
1878 static int
1879 termp_lk_pre(DECL_ARGS)
1880 {
1881 	const struct roff_node *link, *descr, *punct;
1882 
1883 	if ((link = n->child) == NULL)
1884 		return 0;
1885 
1886 	/* Find beginning of trailing punctuation. */
1887 	punct = n->last;
1888 	while (punct != link && punct->flags & NODE_DELIMC)
1889 		punct = punct->prev;
1890 	punct = punct->next;
1891 
1892 	/* Link text. */
1893 	if ((descr = link->next) != NULL && descr != punct) {
1894 		term_fontpush(p, TERMFONT_UNDER);
1895 		while (descr != punct) {
1896 			if (descr->flags & (NODE_DELIMC | NODE_DELIMO))
1897 				p->flags |= TERMP_NOSPACE;
1898 			term_word(p, descr->string);
1899 			descr = descr->next;
1900 		}
1901 		term_fontpop(p);
1902 		p->flags |= TERMP_NOSPACE;
1903 		term_word(p, ":");
1904 	}
1905 
1906 	/* Link target. */
1907 	term_word(p, link->string);
1908 
1909 	/* Trailing punctuation. */
1910 	while (punct != NULL) {
1911 		p->flags |= TERMP_NOSPACE;
1912 		term_word(p, punct->string);
1913 		punct = punct->next;
1914 	}
1915 	return 0;
1916 }
1917 
1918 static int
1919 termp_bk_pre(DECL_ARGS)
1920 {
1921 	switch (n->type) {
1922 	case ROFFT_BLOCK:
1923 		break;
1924 	case ROFFT_HEAD:
1925 		return 0;
1926 	case ROFFT_BODY:
1927 		if (n->parent->args != NULL || n->prev->child == NULL)
1928 			p->flags |= TERMP_PREKEEP;
1929 		break;
1930 	default:
1931 		abort();
1932 	}
1933 	return 1;
1934 }
1935 
1936 static void
1937 termp_bk_post(DECL_ARGS)
1938 {
1939 	if (n->type == ROFFT_BODY)
1940 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
1941 }
1942 
1943 /*
1944  * If we are in an `Rs' and there is a journal present,
1945  * then quote us instead of underlining us (for disambiguation).
1946  */
1947 static void
1948 termp__t_post(DECL_ARGS)
1949 {
1950 	if (n->parent != NULL && n->parent->tok == MDOC_Rs &&
1951 	    n->parent->norm->Rs.quote_T)
1952 		termp_quote_post(p, pair, meta, n);
1953 	termp____post(p, pair, meta, n);
1954 }
1955 
1956 static int
1957 termp__t_pre(DECL_ARGS)
1958 {
1959 	if (n->parent != NULL && n->parent->tok == MDOC_Rs &&
1960 	    n->parent->norm->Rs.quote_T)
1961 		return termp_quote_pre(p, pair, meta, n);
1962 	else
1963 		return termp_under_pre(p, pair, meta, n);
1964 }
1965 
1966 static int
1967 termp_under_pre(DECL_ARGS)
1968 {
1969 	term_fontpush(p, TERMFONT_UNDER);
1970 	return 1;
1971 }
1972 
1973 static int
1974 termp_abort_pre(DECL_ARGS)
1975 {
1976 	abort();
1977 }
1978