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