xref: /openbsd-src/usr.bin/mandoc/mdoc_term.c (revision 5054e3e78af0749a9bb00ba9a024b3ee2d90290f)
1 /*	$Id: mdoc_term.c,v 1.62 2009/10/27 21:40:07 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include <sys/types.h>
18 
19 #include <assert.h>
20 #include <ctype.h>
21 #include <err.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "out.h"
27 #include "term.h"
28 #include "mdoc.h"
29 #include "chars.h"
30 #include "main.h"
31 
32 #define	INDENT		  5
33 #define	HALFINDENT	  3
34 
35 struct	termpair {
36 	struct termpair	 *ppair;
37 	int	  	  flag;
38 	int		  count;
39 };
40 
41 #define	DECL_ARGS struct termp *p, \
42 		  struct termpair *pair, \
43 	  	  const struct mdoc_meta *m, \
44 		  const struct mdoc_node *n
45 
46 struct	termact {
47 	int	(*pre)(DECL_ARGS);
48 	void	(*post)(DECL_ARGS);
49 };
50 
51 static	size_t	  a2width(const struct mdoc_argv *, int);
52 static	size_t	  a2height(const struct mdoc_node *);
53 static	size_t	  a2offs(const struct mdoc_argv *);
54 
55 static	int	  arg_hasattr(int, const struct mdoc_node *);
56 static	int	  arg_getattrs(const int *, int *, size_t,
57 			const struct mdoc_node *);
58 static	int	  arg_getattr(int, const struct mdoc_node *);
59 static	int	  arg_listtype(const struct mdoc_node *);
60 static	void	  print_bvspace(struct termp *,
61 			const struct mdoc_node *,
62 			const struct mdoc_node *);
63 static	void  	  print_node(DECL_ARGS);
64 static	void	  print_head(DECL_ARGS);
65 static	void	  print_body(DECL_ARGS);
66 static	void	  print_foot(DECL_ARGS);
67 
68 static	void	  termp____post(DECL_ARGS);
69 static	void	  termp_an_post(DECL_ARGS);
70 static	void	  termp_aq_post(DECL_ARGS);
71 static	void	  termp_bd_post(DECL_ARGS);
72 static	void	  termp_bl_post(DECL_ARGS);
73 static	void	  termp_bq_post(DECL_ARGS);
74 static	void	  termp_brq_post(DECL_ARGS);
75 static	void	  termp_bx_post(DECL_ARGS);
76 static	void	  termp_d1_post(DECL_ARGS);
77 static	void	  termp_dq_post(DECL_ARGS);
78 static	void	  termp_fd_post(DECL_ARGS);
79 static	void	  termp_fn_post(DECL_ARGS);
80 static	void	  termp_fo_post(DECL_ARGS);
81 static	void	  termp_ft_post(DECL_ARGS);
82 static	void	  termp_in_post(DECL_ARGS);
83 static	void	  termp_it_post(DECL_ARGS);
84 static	void	  termp_lb_post(DECL_ARGS);
85 static	void	  termp_op_post(DECL_ARGS);
86 static	void	  termp_pf_post(DECL_ARGS);
87 static	void	  termp_pq_post(DECL_ARGS);
88 static	void	  termp_qq_post(DECL_ARGS);
89 static	void	  termp_sh_post(DECL_ARGS);
90 static	void	  termp_sq_post(DECL_ARGS);
91 static	void	  termp_ss_post(DECL_ARGS);
92 static	void	  termp_vt_post(DECL_ARGS);
93 
94 static	int	  termp__t_pre(DECL_ARGS);
95 static	int	  termp_an_pre(DECL_ARGS);
96 static	int	  termp_ap_pre(DECL_ARGS);
97 static	int	  termp_aq_pre(DECL_ARGS);
98 static	int	  termp_bd_pre(DECL_ARGS);
99 static	int	  termp_bf_pre(DECL_ARGS);
100 static	int	  termp_bold_pre(DECL_ARGS);
101 static	int	  termp_bq_pre(DECL_ARGS);
102 static	int	  termp_brq_pre(DECL_ARGS);
103 static	int	  termp_bt_pre(DECL_ARGS);
104 static	int	  termp_cd_pre(DECL_ARGS);
105 static	int	  termp_d1_pre(DECL_ARGS);
106 static	int	  termp_dq_pre(DECL_ARGS);
107 static	int	  termp_ex_pre(DECL_ARGS);
108 static	int	  termp_fa_pre(DECL_ARGS);
109 static	int	  termp_fl_pre(DECL_ARGS);
110 static	int	  termp_fn_pre(DECL_ARGS);
111 static	int	  termp_fo_pre(DECL_ARGS);
112 static	int	  termp_ft_pre(DECL_ARGS);
113 static	int	  termp_in_pre(DECL_ARGS);
114 static	int	  termp_it_pre(DECL_ARGS);
115 static	int	  termp_lk_pre(DECL_ARGS);
116 static	int	  termp_nd_pre(DECL_ARGS);
117 static	int	  termp_nm_pre(DECL_ARGS);
118 static	int	  termp_ns_pre(DECL_ARGS);
119 static	int	  termp_op_pre(DECL_ARGS);
120 static	int	  termp_pf_pre(DECL_ARGS);
121 static	int	  termp_pq_pre(DECL_ARGS);
122 static	int	  termp_qq_pre(DECL_ARGS);
123 static	int	  termp_rs_pre(DECL_ARGS);
124 static	int	  termp_rv_pre(DECL_ARGS);
125 static	int	  termp_sh_pre(DECL_ARGS);
126 static	int	  termp_sm_pre(DECL_ARGS);
127 static	int	  termp_sp_pre(DECL_ARGS);
128 static	int	  termp_sq_pre(DECL_ARGS);
129 static	int	  termp_ss_pre(DECL_ARGS);
130 static	int	  termp_under_pre(DECL_ARGS);
131 static	int	  termp_ud_pre(DECL_ARGS);
132 static	int	  termp_xr_pre(DECL_ARGS);
133 static	int	  termp_xx_pre(DECL_ARGS);
134 
135 static	const struct termact termacts[MDOC_MAX] = {
136 	{ termp_ap_pre, NULL }, /* Ap */
137 	{ NULL, NULL }, /* Dd */
138 	{ NULL, NULL }, /* Dt */
139 	{ NULL, NULL }, /* Os */
140 	{ termp_sh_pre, termp_sh_post }, /* Sh */
141 	{ termp_ss_pre, termp_ss_post }, /* Ss */
142 	{ termp_sp_pre, NULL }, /* Pp */
143 	{ termp_d1_pre, termp_d1_post }, /* D1 */
144 	{ termp_d1_pre, termp_d1_post }, /* Dl */
145 	{ termp_bd_pre, termp_bd_post }, /* Bd */
146 	{ NULL, NULL }, /* Ed */
147 	{ NULL, termp_bl_post }, /* Bl */
148 	{ NULL, NULL }, /* El */
149 	{ termp_it_pre, termp_it_post }, /* It */
150 	{ NULL, NULL }, /* Ad */
151 	{ termp_an_pre, termp_an_post }, /* An */
152 	{ termp_under_pre, NULL }, /* Ar */
153 	{ termp_cd_pre, NULL }, /* Cd */
154 	{ termp_bold_pre, NULL }, /* Cm */
155 	{ NULL, NULL }, /* Dv */
156 	{ NULL, NULL }, /* Er */
157 	{ NULL, NULL }, /* Ev */
158 	{ termp_ex_pre, NULL }, /* Ex */
159 	{ termp_fa_pre, NULL }, /* Fa */
160 	{ termp_bold_pre, termp_fd_post }, /* Fd */
161 	{ termp_fl_pre, NULL }, /* Fl */
162 	{ termp_fn_pre, termp_fn_post }, /* Fn */
163 	{ termp_ft_pre, termp_ft_post }, /* Ft */
164 	{ termp_bold_pre, NULL }, /* Ic */
165 	{ termp_in_pre, termp_in_post }, /* In */
166 	{ NULL, NULL }, /* Li */
167 	{ termp_nd_pre, NULL }, /* Nd */
168 	{ termp_nm_pre, NULL }, /* Nm */
169 	{ termp_op_pre, termp_op_post }, /* Op */
170 	{ NULL, NULL }, /* Ot */
171 	{ termp_under_pre, NULL }, /* Pa */
172 	{ termp_rv_pre, NULL }, /* Rv */
173 	{ NULL, NULL }, /* St */
174 	{ termp_under_pre, NULL }, /* Va */
175 	{ termp_under_pre, termp_vt_post }, /* Vt */
176 	{ termp_xr_pre, NULL }, /* Xr */
177 	{ NULL, termp____post }, /* %A */
178 	{ termp_under_pre, termp____post }, /* %B */
179 	{ NULL, termp____post }, /* %D */
180 	{ termp_under_pre, termp____post }, /* %I */
181 	{ termp_under_pre, termp____post }, /* %J */
182 	{ NULL, termp____post }, /* %N */
183 	{ NULL, termp____post }, /* %O */
184 	{ NULL, termp____post }, /* %P */
185 	{ NULL, termp____post }, /* %R */
186 	{ termp__t_pre, termp____post }, /* %T */
187 	{ NULL, termp____post }, /* %V */
188 	{ NULL, NULL }, /* Ac */
189 	{ termp_aq_pre, termp_aq_post }, /* Ao */
190 	{ termp_aq_pre, termp_aq_post }, /* Aq */
191 	{ NULL, NULL }, /* At */
192 	{ NULL, NULL }, /* Bc */
193 	{ termp_bf_pre, NULL }, /* Bf */
194 	{ termp_bq_pre, termp_bq_post }, /* Bo */
195 	{ termp_bq_pre, termp_bq_post }, /* Bq */
196 	{ termp_xx_pre, NULL }, /* Bsx */
197 	{ NULL, termp_bx_post }, /* Bx */
198 	{ NULL, NULL }, /* Db */
199 	{ NULL, NULL }, /* Dc */
200 	{ termp_dq_pre, termp_dq_post }, /* Do */
201 	{ termp_dq_pre, termp_dq_post }, /* Dq */
202 	{ NULL, NULL }, /* Ec */
203 	{ NULL, NULL }, /* Ef */
204 	{ termp_under_pre, NULL }, /* Em */
205 	{ NULL, NULL }, /* Eo */
206 	{ termp_xx_pre, NULL }, /* Fx */
207 	{ termp_bold_pre, NULL }, /* Ms */ /* FIXME: convert to symbol? */
208 	{ NULL, NULL }, /* No */
209 	{ termp_ns_pre, NULL }, /* Ns */
210 	{ termp_xx_pre, NULL }, /* Nx */
211 	{ termp_xx_pre, NULL }, /* Ox */
212 	{ NULL, NULL }, /* Pc */
213 	{ termp_pf_pre, termp_pf_post }, /* Pf */
214 	{ termp_pq_pre, termp_pq_post }, /* Po */
215 	{ termp_pq_pre, termp_pq_post }, /* Pq */
216 	{ NULL, NULL }, /* Qc */
217 	{ termp_sq_pre, termp_sq_post }, /* Ql */
218 	{ termp_qq_pre, termp_qq_post }, /* Qo */
219 	{ termp_qq_pre, termp_qq_post }, /* Qq */
220 	{ NULL, NULL }, /* Re */
221 	{ termp_rs_pre, NULL }, /* Rs */
222 	{ NULL, NULL }, /* Sc */
223 	{ termp_sq_pre, termp_sq_post }, /* So */
224 	{ termp_sq_pre, termp_sq_post }, /* Sq */
225 	{ termp_sm_pre, NULL }, /* Sm */
226 	{ termp_under_pre, NULL }, /* Sx */
227 	{ termp_bold_pre, NULL }, /* Sy */
228 	{ NULL, NULL }, /* Tn */
229 	{ termp_xx_pre, NULL }, /* Ux */
230 	{ NULL, NULL }, /* Xc */
231 	{ NULL, NULL }, /* Xo */
232 	{ termp_fo_pre, termp_fo_post }, /* Fo */
233 	{ NULL, NULL }, /* Fc */
234 	{ termp_op_pre, termp_op_post }, /* Oo */
235 	{ NULL, NULL }, /* Oc */
236 	{ NULL, NULL }, /* Bk */
237 	{ NULL, NULL }, /* Ek */
238 	{ termp_bt_pre, NULL }, /* Bt */
239 	{ NULL, NULL }, /* Hf */
240 	{ NULL, NULL }, /* Fr */
241 	{ termp_ud_pre, NULL }, /* Ud */
242 	{ NULL, termp_lb_post }, /* Lb */
243 	{ termp_sp_pre, NULL }, /* Lp */
244 	{ termp_lk_pre, NULL }, /* Lk */
245 	{ termp_under_pre, NULL }, /* Mt */
246 	{ termp_brq_pre, termp_brq_post }, /* Brq */
247 	{ termp_brq_pre, termp_brq_post }, /* Bro */
248 	{ NULL, NULL }, /* Brc */
249 	{ NULL, termp____post }, /* %C */
250 	{ NULL, NULL }, /* Es */ /* TODO */
251 	{ NULL, NULL }, /* En */ /* TODO */
252 	{ termp_xx_pre, NULL }, /* Dx */
253 	{ NULL, termp____post }, /* %Q */
254 	{ termp_sp_pre, NULL }, /* br */
255 	{ termp_sp_pre, NULL }, /* sp */
256 	{ termp_under_pre, termp____post }, /* %U */
257 };
258 
259 
260 void
261 terminal_mdoc(void *arg, const struct mdoc *mdoc)
262 {
263 	const struct mdoc_node	*n;
264 	const struct mdoc_meta	*m;
265 	struct termp		*p;
266 
267 	p = (struct termp *)arg;
268 
269 	if (NULL == p->symtab)
270 		switch (p->enc) {
271 		case (TERMENC_ASCII):
272 			p->symtab = chars_init(CHARS_ASCII);
273 			break;
274 		default:
275 			abort();
276 			/* NOTREACHED */
277 		}
278 
279 	n = mdoc_node(mdoc);
280 	m = mdoc_meta(mdoc);
281 
282 	print_head(p, NULL, m, n);
283 	if (n->child)
284 		print_body(p, NULL, m, n->child);
285 	print_foot(p, NULL, m, n);
286 }
287 
288 
289 static void
290 print_body(DECL_ARGS)
291 {
292 
293 	print_node(p, pair, m, n);
294 	if (n->next)
295 		print_body(p, pair, m, n->next);
296 }
297 
298 
299 /* ARGSUSED */
300 static void
301 print_node(DECL_ARGS)
302 {
303 	int		 chld, bold, under;
304 	struct termpair	 npair;
305 	size_t		 offset, rmargin;
306 
307 	chld = 1;
308 	offset = p->offset;
309 	rmargin = p->rmargin;
310 	bold = p->bold;
311 	under = p->under;
312 
313 	bzero(&npair, sizeof(struct termpair));
314 	npair.ppair = pair;
315 
316 	if (MDOC_TEXT != n->type) {
317 		if (termacts[n->tok].pre)
318 			chld = (*termacts[n->tok].pre)(p, &npair, m, n);
319 	} else
320 		term_word(p, n->string);
321 	if (chld && n->child)
322 		print_body(p, &npair, m, n->child);
323 
324 	/*
325 	 * XXX - if bold/under were to span scopes, this wouldn't be
326 	 * possible, but because decoration is always in-scope, we can
327 	 * get away with this.
328 	 */
329 
330 	p->bold = bold;
331 	p->under = under;
332 
333 	if (MDOC_TEXT != n->type)
334 		if (termacts[n->tok].post)
335 			(*termacts[n->tok].post)(p, &npair, m, n);
336 
337 	p->offset = offset;
338 	p->rmargin = rmargin;
339 }
340 
341 
342 /* ARGSUSED */
343 static void
344 print_foot(DECL_ARGS)
345 {
346 	char		 buf[DATESIZ];
347 	char		*os;
348 
349 	/*
350 	 * Output the footer in new-groff style, that is, three columns
351 	 * with the middle being the manual date and flanking columns
352 	 * being the operating system:
353 	 *
354 	 * SYSTEM                  DATE                    SYSTEM
355 	 */
356 
357 	if (NULL == (os = malloc(p->rmargin)))
358 		err(EXIT_FAILURE, "malloc");
359 
360 	time2a(m->date, buf, DATESIZ);
361 
362 	(void)strlcpy(os, m->os, p->rmargin);
363 
364 	term_vspace(p);
365 
366 	p->offset = 0;
367 	p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2;
368 	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
369 
370 	term_word(p, os);
371 	term_flushln(p);
372 
373 	p->offset = p->rmargin;
374 	p->rmargin = p->maxrmargin - strlen(os);
375 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
376 
377 	term_word(p, buf);
378 	term_flushln(p);
379 
380 	p->offset = p->rmargin;
381 	p->rmargin = p->maxrmargin;
382 	p->flags &= ~TERMP_NOBREAK;
383 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
384 
385 	term_word(p, os);
386 	term_flushln(p);
387 
388 	p->offset = 0;
389 	p->rmargin = p->maxrmargin;
390 	p->flags = 0;
391 
392 	free(os);
393 }
394 
395 
396 /* FIXME: put in utility library. */
397 /* ARGSUSED */
398 static void
399 print_head(DECL_ARGS)
400 {
401 	char		*buf, *title;
402 
403 	p->rmargin = p->maxrmargin;
404 	p->offset = 0;
405 
406 	if (NULL == (buf = malloc(p->rmargin)))
407 		err(EXIT_FAILURE, "malloc");
408 	if (NULL == (title = malloc(p->rmargin)))
409 		err(EXIT_FAILURE, "malloc");
410 
411 	/*
412 	 * The header is strange.  It has three components, which are
413 	 * really two with the first duplicated.  It goes like this:
414 	 *
415 	 * IDENTIFIER              TITLE                   IDENTIFIER
416 	 *
417 	 * The IDENTIFIER is NAME(SECTION), which is the command-name
418 	 * (if given, or "unknown" if not) followed by the manual page
419 	 * section.  These are given in `Dt'.  The TITLE is a free-form
420 	 * string depending on the manual volume.  If not specified, it
421 	 * switches on the manual section.
422 	 */
423 
424 	assert(m->vol);
425 	(void)strlcpy(buf, m->vol, p->rmargin);
426 
427 	if (m->arch) {
428 		(void)strlcat(buf, " (", p->rmargin);
429 		(void)strlcat(buf, m->arch, p->rmargin);
430 		(void)strlcat(buf, ")", p->rmargin);
431 	}
432 
433 	snprintf(title, p->rmargin, "%s(%d)", m->title, m->msec);
434 
435 	p->offset = 0;
436 	p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2;
437 	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
438 
439 	term_word(p, title);
440 	term_flushln(p);
441 
442 	p->offset = p->rmargin;
443 	p->rmargin = p->maxrmargin - strlen(title);
444 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
445 
446 	term_word(p, buf);
447 	term_flushln(p);
448 
449 	p->offset = p->rmargin;
450 	p->rmargin = p->maxrmargin;
451 	p->flags &= ~TERMP_NOBREAK;
452 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
453 
454 	term_word(p, title);
455 	term_flushln(p);
456 
457 	p->offset = 0;
458 	p->rmargin = p->maxrmargin;
459 	p->flags &= ~TERMP_NOSPACE;
460 
461 	free(title);
462 	free(buf);
463 }
464 
465 
466 static size_t
467 a2height(const struct mdoc_node *n)
468 {
469 	struct roffsu	 su;
470 
471 	assert(MDOC_TEXT == n->type);
472 	assert(n->string);
473 	if ( ! a2roffsu(n->string, &su, SCALE_VS))
474 		SCALE_VS_INIT(&su, strlen(n->string));
475 
476 	return(term_vspan(&su));
477 }
478 
479 
480 static size_t
481 a2width(const struct mdoc_argv *arg, int pos)
482 {
483 	struct roffsu	 su;
484 
485 	assert(arg->value[pos]);
486 	if ( ! a2roffsu(arg->value[pos], &su, SCALE_MAX))
487 		SCALE_HS_INIT(&su, strlen(arg->value[pos]));
488 
489 	/* XXX: pachemu? */
490 	return(term_hspan(&su) + 2);
491 }
492 
493 
494 static int
495 arg_listtype(const struct mdoc_node *n)
496 {
497 	int		 i, len;
498 
499 	assert(MDOC_BLOCK == n->type);
500 
501 	len = (int)(n->args ? n->args->argc : 0);
502 
503 	for (i = 0; i < len; i++)
504 		switch (n->args->argv[i].arg) {
505 		case (MDOC_Bullet):
506 			/* FALLTHROUGH */
507 		case (MDOC_Dash):
508 			/* FALLTHROUGH */
509 		case (MDOC_Enum):
510 			/* FALLTHROUGH */
511 		case (MDOC_Hyphen):
512 			/* FALLTHROUGH */
513 		case (MDOC_Tag):
514 			/* FALLTHROUGH */
515 		case (MDOC_Inset):
516 			/* FALLTHROUGH */
517 		case (MDOC_Diag):
518 			/* FALLTHROUGH */
519 		case (MDOC_Item):
520 			/* FALLTHROUGH */
521 		case (MDOC_Column):
522 			/* FALLTHROUGH */
523 		case (MDOC_Hang):
524 			/* FALLTHROUGH */
525 		case (MDOC_Ohang):
526 			return(n->args->argv[i].arg);
527 		default:
528 			break;
529 		}
530 
531 	return(-1);
532 }
533 
534 
535 static size_t
536 a2offs(const struct mdoc_argv *arg)
537 {
538 	struct roffsu	 su;
539 
540 	if ('\0' == arg->value[0][0])
541 		return(0);
542 	else if (0 == strcmp(arg->value[0], "left"))
543 		return(0);
544 	else if (0 == strcmp(arg->value[0], "indent"))
545 		return(INDENT + 1);
546 	else if (0 == strcmp(arg->value[0], "indent-two"))
547 		return((INDENT + 1) * 2);
548 	else if ( ! a2roffsu(arg->value[0], &su, SCALE_MAX))
549 		SCALE_HS_INIT(&su, strlen(arg->value[0]));
550 
551 	return(term_hspan(&su));
552 }
553 
554 
555 static int
556 arg_hasattr(int arg, const struct mdoc_node *n)
557 {
558 
559 	return(-1 != arg_getattr(arg, n));
560 }
561 
562 
563 static int
564 arg_getattr(int v, const struct mdoc_node *n)
565 {
566 	int		 val;
567 
568 	return(arg_getattrs(&v, &val, 1, n) ? val : -1);
569 }
570 
571 
572 static int
573 arg_getattrs(const int *keys, int *vals,
574 		size_t sz, const struct mdoc_node *n)
575 {
576 	int		 i, j, k;
577 
578 	if (NULL == n->args)
579 		return(0);
580 
581 	for (k = i = 0; i < (int)n->args->argc; i++)
582 		for (j = 0; j < (int)sz; j++)
583 			if (n->args->argv[i].arg == keys[j]) {
584 				vals[j] = i;
585 				k++;
586 			}
587 	return(k);
588 }
589 
590 
591 static void
592 print_bvspace(struct termp *p,
593 		const struct mdoc_node *bl,
594 		const struct mdoc_node *n)
595 {
596 	const struct mdoc_node	*nn;
597 
598 	term_newln(p);
599 	if (arg_hasattr(MDOC_Compact, bl))
600 		return;
601 
602 	/* Do not vspace directly after Ss/Sh. */
603 
604 	for (nn = n; nn; nn = nn->parent) {
605 		if (MDOC_BLOCK != nn->type)
606 			continue;
607 		if (MDOC_Ss == nn->tok)
608 			return;
609 		if (MDOC_Sh == nn->tok)
610 			return;
611 		if (NULL == nn->prev)
612 			continue;
613 		break;
614 	}
615 
616 	/* A `-column' does not assert vspace within the list. */
617 
618 	if (MDOC_Bl == bl->tok && arg_hasattr(MDOC_Column, bl))
619 		if (n->prev && MDOC_It == n->prev->tok)
620 			return;
621 
622 	/* A `-diag' without body does not vspace. */
623 
624 	if (MDOC_Bl == bl->tok && arg_hasattr(MDOC_Diag, bl))
625 		if (n->prev && MDOC_It == n->prev->tok) {
626 			assert(n->prev->body);
627 			if (NULL == n->prev->body->child)
628 				return;
629 		}
630 
631 	term_vspace(p);
632 }
633 
634 
635 /* ARGSUSED */
636 static int
637 termp_dq_pre(DECL_ARGS)
638 {
639 
640 	if (MDOC_BODY != n->type)
641 		return(1);
642 
643 	term_word(p, "\\(lq");
644 	p->flags |= TERMP_NOSPACE;
645 	return(1);
646 }
647 
648 
649 /* ARGSUSED */
650 static void
651 termp_dq_post(DECL_ARGS)
652 {
653 
654 	if (MDOC_BODY != n->type)
655 		return;
656 
657 	p->flags |= TERMP_NOSPACE;
658 	term_word(p, "\\(rq");
659 }
660 
661 
662 /* ARGSUSED */
663 static int
664 termp_it_pre(DECL_ARGS)
665 {
666 	const struct mdoc_node *bl, *nn;
667 	char		        buf[7];
668 	int		        i, type, keys[3], vals[3];
669 	size_t		        width, offset;
670 
671 	if (MDOC_BLOCK == n->type) {
672 		print_bvspace(p, n->parent->parent, n);
673 		return(1);
674 	}
675 
676 	bl = n->parent->parent->parent;
677 
678 	/* Save parent attributes. */
679 
680 	pair->flag = p->flags;
681 
682 	/* Get list width and offset. */
683 
684 	keys[0] = MDOC_Width;
685 	keys[1] = MDOC_Offset;
686 	keys[2] = MDOC_Column;
687 
688 	vals[0] = vals[1] = vals[2] = -1;
689 
690 	width = offset = 0;
691 
692 	(void)arg_getattrs(keys, vals, 3, bl);
693 
694 	type = arg_listtype(bl);
695 	assert(-1 != type);
696 
697 	/* Calculate real width and offset. */
698 
699 	switch (type) {
700 	case (MDOC_Column):
701 		if (MDOC_BODY == n->type)
702 			break;
703 		/*
704 		 * Work around groff's column handling.  The offset is
705 		 * equal to the sum of all widths leading to the current
706 		 * column (plus the -offset value).  If this column
707 		 * exceeds the stated number of columns, the width is
708 		 * set as 0, else it's the stated column width (later
709 		 * the 0 will be adjusted to default 10 or, if in the
710 		 * last column case, set to stretch to the margin).
711 		 */
712 		for (i = 0, nn = n->prev; nn &&
713 				i < (int)bl->args->argv[vals[2]].sz;
714 				nn = nn->prev, i++)
715 			offset += a2width
716 				(&bl->args->argv[vals[2]], i);
717 
718 		/* Whether exceeds maximum column. */
719 		if (i < (int)bl->args->argv[vals[2]].sz)
720 			width = a2width(&bl->args->argv[vals[2]], i);
721 		else
722 			width = 0;
723 
724 		if (vals[1] >= 0)
725 			offset += a2offs(&bl->args->argv[vals[1]]);
726 		break;
727 	default:
728 		if (vals[0] >= 0)
729 			width = a2width(&bl->args->argv[vals[0]], 0);
730 		if (vals[1] >= 0)
731 			offset += a2offs(&bl->args->argv[vals[1]]);
732 		break;
733 	}
734 
735 	/*
736 	 * List-type can override the width in the case of fixed-head
737 	 * values (bullet, dash/hyphen, enum).  Tags need a non-zero
738 	 * offset.
739 	 */
740 
741 	switch (type) {
742 	case (MDOC_Bullet):
743 		/* FALLTHROUGH */
744 	case (MDOC_Dash):
745 		/* FALLTHROUGH */
746 	case (MDOC_Hyphen):
747 		if (width < 4)
748 			width = 4;
749 		break;
750 	case (MDOC_Enum):
751 		if (width < 5)
752 			width = 5;
753 		break;
754 	case (MDOC_Hang):
755 		if (0 == width)
756 			width = 8;
757 		break;
758 	case (MDOC_Column):
759 		/* FALLTHROUGH */
760 	case (MDOC_Tag):
761 		if (0 == width)
762 			width = 10;
763 		break;
764 	default:
765 		break;
766 	}
767 
768 	/*
769 	 * Whitespace control.  Inset bodies need an initial space,
770 	 * while diagonal bodies need two.
771 	 */
772 
773 	p->flags |= TERMP_NOSPACE;
774 
775 	switch (type) {
776 	case (MDOC_Diag):
777 		if (MDOC_BODY == n->type)
778 			term_word(p, "\\ \\ ");
779 		break;
780 	case (MDOC_Inset):
781 		if (MDOC_BODY == n->type)
782 			term_word(p, "\\ ");
783 		break;
784 	default:
785 		break;
786 	}
787 
788 	p->flags |= TERMP_NOSPACE;
789 
790 	switch (type) {
791 	case (MDOC_Diag):
792 		if (MDOC_HEAD == n->type)
793 			p->bold++;
794 		break;
795 	default:
796 		break;
797 	}
798 
799 	/*
800 	 * Pad and break control.  This is the tricker part.  Lists with
801 	 * set right-margins for the head get TERMP_NOBREAK because, if
802 	 * they overrun the margin, they wrap to the new margin.
803 	 * Correspondingly, the body for these types don't left-pad, as
804 	 * the head will pad out to to the right.
805 	 */
806 
807 	switch (type) {
808 	case (MDOC_Bullet):
809 		/* FALLTHROUGH */
810 	case (MDOC_Dash):
811 		/* FALLTHROUGH */
812 	case (MDOC_Enum):
813 		/* FALLTHROUGH */
814 	case (MDOC_Hyphen):
815 		if (MDOC_HEAD == n->type)
816 			p->flags |= TERMP_NOBREAK;
817 		else
818 			p->flags |= TERMP_NOLPAD;
819 		break;
820 	case (MDOC_Hang):
821 		if (MDOC_HEAD == n->type)
822 			p->flags |= TERMP_NOBREAK;
823 		else
824 			p->flags |= TERMP_NOLPAD;
825 
826 		if (MDOC_HEAD != n->type)
827 			break;
828 
829 		/*
830 		 * This is ugly.  If `-hang' is specified and the body
831 		 * is a `Bl' or `Bd', then we want basically to nullify
832 		 * the "overstep" effect in term_flushln() and treat
833 		 * this as a `-ohang' list instead.
834 		 */
835 		if (n->next->child &&
836 				(MDOC_Bl == n->next->child->tok ||
837 				 MDOC_Bd == n->next->child->tok)) {
838 			p->flags &= ~TERMP_NOBREAK;
839 			p->flags &= ~TERMP_NOLPAD;
840 		} else
841 			p->flags |= TERMP_HANG;
842 		break;
843 	case (MDOC_Tag):
844 		if (MDOC_HEAD == n->type)
845 			p->flags |= TERMP_NOBREAK | TERMP_TWOSPACE;
846 		else
847 			p->flags |= TERMP_NOLPAD;
848 
849 		if (MDOC_HEAD != n->type)
850 			break;
851 		if (NULL == n->next || NULL == n->next->child)
852 			p->flags |= TERMP_DANGLE;
853 		break;
854 	case (MDOC_Column):
855 		if (MDOC_HEAD == n->type) {
856 			assert(n->next);
857 			if (MDOC_BODY == n->next->type)
858 				p->flags &= ~TERMP_NOBREAK;
859 			else
860 				p->flags |= TERMP_NOBREAK;
861 			if (n->prev)
862 				p->flags |= TERMP_NOLPAD;
863 		}
864 		break;
865 	case (MDOC_Diag):
866 		if (MDOC_HEAD == n->type)
867 			p->flags |= TERMP_NOBREAK;
868 		break;
869 	default:
870 		break;
871 	}
872 
873 	/*
874 	 * Margin control.  Set-head-width lists have their right
875 	 * margins shortened.  The body for these lists has the offset
876 	 * necessarily lengthened.  Everybody gets the offset.
877 	 */
878 
879 	p->offset += offset;
880 
881 	switch (type) {
882 	case (MDOC_Hang):
883 		/*
884 		 * Same stipulation as above, regarding `-hang'.  We
885 		 * don't want to recalculate rmargin and offsets when
886 		 * using `Bd' or `Bl' within `-hang' overstep lists.
887 		 */
888 		if (MDOC_HEAD == n->type && n->next->child &&
889 				(MDOC_Bl == n->next->child->tok ||
890 				 MDOC_Bd == n->next->child->tok))
891 			break;
892 		/* FALLTHROUGH */
893 	case (MDOC_Bullet):
894 		/* FALLTHROUGH */
895 	case (MDOC_Dash):
896 		/* FALLTHROUGH */
897 	case (MDOC_Enum):
898 		/* FALLTHROUGH */
899 	case (MDOC_Hyphen):
900 		/* FALLTHROUGH */
901 	case (MDOC_Tag):
902 		assert(width);
903 		if (MDOC_HEAD == n->type)
904 			p->rmargin = p->offset + width;
905 		else
906 			p->offset += width;
907 		break;
908 	case (MDOC_Column):
909 		assert(width);
910 		p->rmargin = p->offset + width;
911 		/*
912 		 * XXX - this behaviour is not documented: the
913 		 * right-most column is filled to the right margin.
914 		 */
915 		if (MDOC_HEAD == n->type &&
916 				MDOC_BODY == n->next->type)
917 			p->rmargin = p->maxrmargin;
918 		break;
919 	default:
920 		break;
921 	}
922 
923 	/*
924 	 * The dash, hyphen, bullet and enum lists all have a special
925 	 * HEAD character (temporarily bold, in some cases).
926 	 */
927 
928 	if (MDOC_HEAD == n->type)
929 		switch (type) {
930 		case (MDOC_Bullet):
931 			p->bold++;
932 			term_word(p, "\\[bu]");
933 			p->bold--;
934 			break;
935 		case (MDOC_Dash):
936 			/* FALLTHROUGH */
937 		case (MDOC_Hyphen):
938 			p->bold++;
939 			term_word(p, "\\(hy");
940 			p->bold--;
941 			break;
942 		case (MDOC_Enum):
943 			(pair->ppair->ppair->count)++;
944 			(void)snprintf(buf, sizeof(buf), "%d.",
945 					pair->ppair->ppair->count);
946 			term_word(p, buf);
947 			break;
948 		default:
949 			break;
950 		}
951 
952 	/*
953 	 * If we're not going to process our children, indicate so here.
954 	 */
955 
956 	switch (type) {
957 	case (MDOC_Bullet):
958 		/* FALLTHROUGH */
959 	case (MDOC_Item):
960 		/* FALLTHROUGH */
961 	case (MDOC_Dash):
962 		/* FALLTHROUGH */
963 	case (MDOC_Hyphen):
964 		/* FALLTHROUGH */
965 	case (MDOC_Enum):
966 		if (MDOC_HEAD == n->type)
967 			return(0);
968 		break;
969 	case (MDOC_Column):
970 		if (MDOC_BODY == n->type)
971 			return(0);
972 		break;
973 	default:
974 		break;
975 	}
976 
977 	return(1);
978 }
979 
980 
981 /* ARGSUSED */
982 static void
983 termp_it_post(DECL_ARGS)
984 {
985 	int		   type;
986 
987 	if (MDOC_BODY != n->type && MDOC_HEAD != n->type)
988 		return;
989 
990 	type = arg_listtype(n->parent->parent->parent);
991 	assert(-1 != type);
992 
993 	switch (type) {
994 	case (MDOC_Item):
995 		/* FALLTHROUGH */
996 	case (MDOC_Diag):
997 		/* FALLTHROUGH */
998 	case (MDOC_Inset):
999 		if (MDOC_BODY == n->type)
1000 			term_flushln(p);
1001 		break;
1002 	case (MDOC_Column):
1003 		if (MDOC_HEAD == n->type)
1004 			term_flushln(p);
1005 		break;
1006 	default:
1007 		term_flushln(p);
1008 		break;
1009 	}
1010 
1011 	p->flags = pair->flag;
1012 }
1013 
1014 
1015 /* ARGSUSED */
1016 static int
1017 termp_nm_pre(DECL_ARGS)
1018 {
1019 
1020 	if (SEC_SYNOPSIS == n->sec)
1021 		term_newln(p);
1022 	p->bold++;
1023 	if (NULL == n->child)
1024 		term_word(p, m->name);
1025 	return(1);
1026 }
1027 
1028 
1029 /* ARGSUSED */
1030 static int
1031 termp_fl_pre(DECL_ARGS)
1032 {
1033 
1034 	p->bold++;
1035 	term_word(p, "\\-");
1036 	p->flags |= TERMP_NOSPACE;
1037 	return(1);
1038 }
1039 
1040 
1041 /* ARGSUSED */
1042 static int
1043 termp_an_pre(DECL_ARGS)
1044 {
1045 
1046 	if (NULL == n->child)
1047 		return(1);
1048 
1049 	/*
1050 	 * If not in the AUTHORS section, `An -split' will cause
1051 	 * newlines to occur before the author name.  If in the AUTHORS
1052 	 * section, by default, the first `An' invocation is nosplit,
1053 	 * then all subsequent ones, regardless of whether interspersed
1054 	 * with other macros/text, are split.  -split, in this case,
1055 	 * will override the condition of the implied first -nosplit.
1056 	 */
1057 
1058 	if (n->sec == SEC_AUTHORS) {
1059 		if ( ! (TERMP_ANPREC & p->flags)) {
1060 			if (TERMP_SPLIT & p->flags)
1061 				term_newln(p);
1062 			return(1);
1063 		}
1064 		if (TERMP_NOSPLIT & p->flags)
1065 			return(1);
1066 		term_newln(p);
1067 		return(1);
1068 	}
1069 
1070 	if (TERMP_SPLIT & p->flags)
1071 		term_newln(p);
1072 
1073 	return(1);
1074 }
1075 
1076 
1077 /* ARGSUSED */
1078 static void
1079 termp_an_post(DECL_ARGS)
1080 {
1081 
1082 	if (n->child) {
1083 		if (SEC_AUTHORS == n->sec)
1084 			p->flags |= TERMP_ANPREC;
1085 		return;
1086 	}
1087 
1088 	if (arg_getattr(MDOC_Split, n) > -1) {
1089 		p->flags &= ~TERMP_NOSPLIT;
1090 		p->flags |= TERMP_SPLIT;
1091 	} else {
1092 		p->flags &= ~TERMP_SPLIT;
1093 		p->flags |= TERMP_NOSPLIT;
1094 	}
1095 
1096 }
1097 
1098 
1099 /* ARGSUSED */
1100 static int
1101 termp_ns_pre(DECL_ARGS)
1102 {
1103 
1104 	p->flags |= TERMP_NOSPACE;
1105 	return(1);
1106 }
1107 
1108 
1109 /* ARGSUSED */
1110 static int
1111 termp_rs_pre(DECL_ARGS)
1112 {
1113 
1114 	if (SEC_SEE_ALSO != n->sec)
1115 		return(1);
1116 	if (MDOC_BLOCK == n->type && n->prev)
1117 		term_vspace(p);
1118 	return(1);
1119 }
1120 
1121 
1122 /* ARGSUSED */
1123 static int
1124 termp_rv_pre(DECL_ARGS)
1125 {
1126 	const struct mdoc_node	*nn;
1127 
1128 	term_newln(p);
1129 	term_word(p, "The");
1130 
1131 	for (nn = n->child; nn; nn = nn->next) {
1132 		p->bold++;
1133 		term_word(p, nn->string);
1134 		p->bold--;
1135 		p->flags |= TERMP_NOSPACE;
1136 		if (nn->next && NULL == nn->next->next)
1137 			term_word(p, "(), and");
1138 		else if (nn->next)
1139 			term_word(p, "(),");
1140 		else
1141 			term_word(p, "()");
1142 	}
1143 
1144 	if (n->child->next)
1145 		term_word(p, "functions return");
1146 	else
1147 		term_word(p, "function returns");
1148 
1149        	term_word(p, "the value 0 if successful; otherwise the value "
1150 			"-1 is returned and the global variable");
1151 
1152 	p->under++;
1153 	term_word(p, "errno");
1154 	p->under--;
1155 
1156        	term_word(p, "is set to indicate the error.");
1157 
1158 	return(0);
1159 }
1160 
1161 
1162 /* ARGSUSED */
1163 static int
1164 termp_ex_pre(DECL_ARGS)
1165 {
1166 	const struct mdoc_node	*nn;
1167 
1168 	term_word(p, "The");
1169 
1170 	for (nn = n->child; nn; nn = nn->next) {
1171 		p->bold++;
1172 		term_word(p, nn->string);
1173 		p->bold--;
1174 		p->flags |= TERMP_NOSPACE;
1175 		if (nn->next && NULL == nn->next->next)
1176 			term_word(p, ", and");
1177 		else if (nn->next)
1178 			term_word(p, ",");
1179 		else
1180 			p->flags &= ~TERMP_NOSPACE;
1181 	}
1182 
1183 	if (n->child->next)
1184 		term_word(p, "utilities exit");
1185 	else
1186 		term_word(p, "utility exits");
1187 
1188        	term_word(p, "0 on success, and >0 if an error occurs.");
1189 
1190 	return(0);
1191 }
1192 
1193 
1194 /* ARGSUSED */
1195 static int
1196 termp_nd_pre(DECL_ARGS)
1197 {
1198 
1199 	if (MDOC_BODY != n->type)
1200 		return(1);
1201 
1202 #if defined(__OpenBSD__) || defined(__linux__)
1203 	term_word(p, "\\(en");
1204 #else
1205 	term_word(p, "\\(em");
1206 #endif
1207 	return(1);
1208 }
1209 
1210 
1211 /* ARGSUSED */
1212 static void
1213 termp_bl_post(DECL_ARGS)
1214 {
1215 
1216 	if (MDOC_BLOCK == n->type)
1217 		term_newln(p);
1218 }
1219 
1220 
1221 /* ARGSUSED */
1222 static void
1223 termp_op_post(DECL_ARGS)
1224 {
1225 
1226 	if (MDOC_BODY != n->type)
1227 		return;
1228 	p->flags |= TERMP_NOSPACE;
1229 	term_word(p, "\\(rB");
1230 }
1231 
1232 
1233 /* ARGSUSED */
1234 static int
1235 termp_xr_pre(DECL_ARGS)
1236 {
1237 	const struct mdoc_node *nn;
1238 
1239 	assert(n->child && MDOC_TEXT == n->child->type);
1240 	nn = n->child;
1241 
1242 	term_word(p, nn->string);
1243 	if (NULL == (nn = nn->next))
1244 		return(0);
1245 	p->flags |= TERMP_NOSPACE;
1246 	term_word(p, "(");
1247 	p->flags |= TERMP_NOSPACE;
1248 	term_word(p, nn->string);
1249 	p->flags |= TERMP_NOSPACE;
1250 	term_word(p, ")");
1251 
1252 	return(0);
1253 }
1254 
1255 
1256 /* ARGSUSED */
1257 static void
1258 termp_vt_post(DECL_ARGS)
1259 {
1260 
1261 	if (n->sec != SEC_SYNOPSIS)
1262 		return;
1263 	if (n->next && MDOC_Vt == n->next->tok)
1264 		term_newln(p);
1265 	else if (n->next)
1266 		term_vspace(p);
1267 }
1268 
1269 
1270 /* ARGSUSED */
1271 static int
1272 termp_bold_pre(DECL_ARGS)
1273 {
1274 
1275 	p->bold++;
1276 	return(1);
1277 }
1278 
1279 
1280 /* ARGSUSED */
1281 static void
1282 termp_fd_post(DECL_ARGS)
1283 {
1284 
1285 	if (n->sec != SEC_SYNOPSIS)
1286 		return;
1287 
1288 	term_newln(p);
1289 	if (n->next && MDOC_Fd != n->next->tok)
1290 		term_vspace(p);
1291 }
1292 
1293 
1294 /* ARGSUSED */
1295 static int
1296 termp_sh_pre(DECL_ARGS)
1297 {
1298 
1299 	/* No vspace between consecutive `Sh' calls. */
1300 
1301 	switch (n->type) {
1302 	case (MDOC_BLOCK):
1303 		if (n->prev && MDOC_Sh == n->prev->tok)
1304 			if (NULL == n->prev->body->child)
1305 				break;
1306 		term_vspace(p);
1307 		break;
1308 	case (MDOC_HEAD):
1309 		p->bold++;
1310 		break;
1311 	case (MDOC_BODY):
1312 		p->offset = INDENT;
1313 		break;
1314 	default:
1315 		break;
1316 	}
1317 	return(1);
1318 }
1319 
1320 
1321 /* ARGSUSED */
1322 static void
1323 termp_sh_post(DECL_ARGS)
1324 {
1325 
1326 	switch (n->type) {
1327 	case (MDOC_HEAD):
1328 		term_newln(p);
1329 		break;
1330 	case (MDOC_BODY):
1331 		term_newln(p);
1332 		p->offset = 0;
1333 		break;
1334 	default:
1335 		break;
1336 	}
1337 }
1338 
1339 
1340 /* ARGSUSED */
1341 static int
1342 termp_op_pre(DECL_ARGS)
1343 {
1344 
1345 	switch (n->type) {
1346 	case (MDOC_BODY):
1347 		term_word(p, "\\(lB");
1348 		p->flags |= TERMP_NOSPACE;
1349 		break;
1350 	default:
1351 		break;
1352 	}
1353 	return(1);
1354 }
1355 
1356 
1357 /* ARGSUSED */
1358 static int
1359 termp_bt_pre(DECL_ARGS)
1360 {
1361 
1362 	term_word(p, "is currently in beta test.");
1363 	return(0);
1364 }
1365 
1366 
1367 /* ARGSUSED */
1368 static void
1369 termp_lb_post(DECL_ARGS)
1370 {
1371 
1372 	if (SEC_LIBRARY == n->sec)
1373 		term_newln(p);
1374 }
1375 
1376 
1377 /* ARGSUSED */
1378 static int
1379 termp_ud_pre(DECL_ARGS)
1380 {
1381 
1382 	term_word(p, "currently under development.");
1383 	return(0);
1384 }
1385 
1386 
1387 /* ARGSUSED */
1388 static int
1389 termp_d1_pre(DECL_ARGS)
1390 {
1391 
1392 	if (MDOC_BLOCK != n->type)
1393 		return(1);
1394 	term_newln(p);
1395 	p->offset += (INDENT + 1);
1396 	return(1);
1397 }
1398 
1399 
1400 /* ARGSUSED */
1401 static void
1402 termp_d1_post(DECL_ARGS)
1403 {
1404 
1405 	if (MDOC_BLOCK != n->type)
1406 		return;
1407 	term_newln(p);
1408 }
1409 
1410 
1411 /* ARGSUSED */
1412 static int
1413 termp_aq_pre(DECL_ARGS)
1414 {
1415 
1416 	if (MDOC_BODY != n->type)
1417 		return(1);
1418 	term_word(p, "\\(la");
1419 	p->flags |= TERMP_NOSPACE;
1420 	return(1);
1421 }
1422 
1423 
1424 /* ARGSUSED */
1425 static void
1426 termp_aq_post(DECL_ARGS)
1427 {
1428 
1429 	if (MDOC_BODY != n->type)
1430 		return;
1431 	p->flags |= TERMP_NOSPACE;
1432 	term_word(p, "\\(ra");
1433 }
1434 
1435 
1436 /* ARGSUSED */
1437 static int
1438 termp_ft_pre(DECL_ARGS)
1439 {
1440 
1441 	if (SEC_SYNOPSIS == n->sec)
1442 		if (n->prev && MDOC_Fo == n->prev->tok)
1443 			term_vspace(p);
1444 	p->under++;
1445 	return(1);
1446 }
1447 
1448 
1449 /* ARGSUSED */
1450 static void
1451 termp_ft_post(DECL_ARGS)
1452 {
1453 
1454 	if (SEC_SYNOPSIS == n->sec)
1455 		term_newln(p);
1456 }
1457 
1458 
1459 /* ARGSUSED */
1460 static int
1461 termp_fn_pre(DECL_ARGS)
1462 {
1463 	const struct mdoc_node	*nn;
1464 
1465 	p->bold++;
1466 	term_word(p, n->child->string);
1467 	p->bold--;
1468 
1469 	p->flags |= TERMP_NOSPACE;
1470 	term_word(p, "(");
1471 
1472 	for (nn = n->child->next; nn; nn = nn->next) {
1473 		p->under++;
1474 		term_word(p, nn->string);
1475 		p->under--;
1476 		if (nn->next)
1477 			term_word(p, ",");
1478 	}
1479 
1480 	term_word(p, ")");
1481 
1482 	if (SEC_SYNOPSIS == n->sec)
1483 		term_word(p, ";");
1484 
1485 	return(0);
1486 }
1487 
1488 
1489 /* ARGSUSED */
1490 static void
1491 termp_fn_post(DECL_ARGS)
1492 {
1493 
1494 	if (n->sec == SEC_SYNOPSIS && n->next)
1495 		term_vspace(p);
1496 }
1497 
1498 
1499 /* ARGSUSED */
1500 static int
1501 termp_fa_pre(DECL_ARGS)
1502 {
1503 	const struct mdoc_node	*nn;
1504 
1505 	if (n->parent->tok != MDOC_Fo) {
1506 		p->under++;
1507 		return(1);
1508 	}
1509 
1510 	for (nn = n->child; nn; nn = nn->next) {
1511 		p->under++;
1512 		term_word(p, nn->string);
1513 		p->under--;
1514 		if (nn->next)
1515 			term_word(p, ",");
1516 	}
1517 
1518 	if (n->child && n->next && n->next->tok == MDOC_Fa)
1519 		term_word(p, ",");
1520 
1521 	return(0);
1522 }
1523 
1524 
1525 /* ARGSUSED */
1526 static int
1527 termp_bd_pre(DECL_ARGS)
1528 {
1529 	int	         	 i, type;
1530 	const struct mdoc_node	*nn;
1531 
1532 	if (MDOC_BLOCK == n->type) {
1533 		print_bvspace(p, n, n);
1534 		return(1);
1535 	} else if (MDOC_BODY != n->type)
1536 		return(1);
1537 
1538 	nn = n->parent;
1539 
1540 	for (type = -1, i = 0; i < (int)nn->args->argc; i++) {
1541 		switch (nn->args->argv[i].arg) {
1542 		case (MDOC_Centred):
1543 			/* FALLTHROUGH */
1544 		case (MDOC_Ragged):
1545 			/* FALLTHROUGH */
1546 		case (MDOC_Filled):
1547 			/* FALLTHROUGH */
1548 		case (MDOC_Unfilled):
1549 			/* FALLTHROUGH */
1550 		case (MDOC_Literal):
1551 			type = nn->args->argv[i].arg;
1552 			break;
1553 		case (MDOC_Offset):
1554 			p->offset += a2offs(&nn->args->argv[i]);
1555 			break;
1556 		default:
1557 			break;
1558 		}
1559 	}
1560 
1561 	/*
1562 	 * If -ragged or -filled are specified, the block does nothing
1563 	 * but change the indentation.  If -unfilled or -literal are
1564 	 * specified, text is printed exactly as entered in the display:
1565 	 * for macro lines, a newline is appended to the line.  Blank
1566 	 * lines are allowed.
1567 	 */
1568 
1569 	assert(type > -1);
1570 	if (MDOC_Literal != type && MDOC_Unfilled != type)
1571 		return(1);
1572 
1573 	for (nn = n->child; nn; nn = nn->next) {
1574 		p->flags |= TERMP_NOSPACE;
1575 		print_node(p, pair, m, nn);
1576 		if (NULL == nn->next)
1577 			continue;
1578 		if (nn->prev && nn->prev->line < nn->line)
1579 			term_flushln(p);
1580 		else if (NULL == nn->prev)
1581 			term_flushln(p);
1582 	}
1583 
1584 	return(0);
1585 }
1586 
1587 
1588 /* ARGSUSED */
1589 static void
1590 termp_bd_post(DECL_ARGS)
1591 {
1592 
1593 	if (MDOC_BODY != n->type)
1594 		return;
1595 	p->flags |= TERMP_NOSPACE;
1596 	term_flushln(p);
1597 }
1598 
1599 
1600 /* ARGSUSED */
1601 static int
1602 termp_qq_pre(DECL_ARGS)
1603 {
1604 
1605 	if (MDOC_BODY != n->type)
1606 		return(1);
1607 	term_word(p, "\"");
1608 	p->flags |= TERMP_NOSPACE;
1609 	return(1);
1610 }
1611 
1612 
1613 /* ARGSUSED */
1614 static void
1615 termp_qq_post(DECL_ARGS)
1616 {
1617 
1618 	if (MDOC_BODY != n->type)
1619 		return;
1620 	p->flags |= TERMP_NOSPACE;
1621 	term_word(p, "\"");
1622 }
1623 
1624 
1625 /* ARGSUSED */
1626 static void
1627 termp_bx_post(DECL_ARGS)
1628 {
1629 
1630 	if (n->child)
1631 		p->flags |= TERMP_NOSPACE;
1632 	term_word(p, "BSD");
1633 }
1634 
1635 
1636 /* ARGSUSED */
1637 static int
1638 termp_xx_pre(DECL_ARGS)
1639 {
1640 	const char	*pp;
1641 
1642 	pp = NULL;
1643 	switch (n->tok) {
1644 	case (MDOC_Bsx):
1645 		pp = "BSDI BSD/OS";
1646 		break;
1647 	case (MDOC_Dx):
1648 		pp = "DragonFlyBSD";
1649 		break;
1650 	case (MDOC_Fx):
1651 		pp = "FreeBSD";
1652 		break;
1653 	case (MDOC_Nx):
1654 		pp = "NetBSD";
1655 		break;
1656 	case (MDOC_Ox):
1657 		pp = "OpenBSD";
1658 		break;
1659 	case (MDOC_Ux):
1660 		pp = "UNIX";
1661 		break;
1662 	default:
1663 		break;
1664 	}
1665 
1666 	assert(pp);
1667 	term_word(p, pp);
1668 	return(1);
1669 }
1670 
1671 
1672 /* ARGSUSED */
1673 static int
1674 termp_sq_pre(DECL_ARGS)
1675 {
1676 
1677 	if (MDOC_BODY != n->type)
1678 		return(1);
1679 	term_word(p, "\\(oq");
1680 	p->flags |= TERMP_NOSPACE;
1681 	return(1);
1682 }
1683 
1684 
1685 /* ARGSUSED */
1686 static void
1687 termp_sq_post(DECL_ARGS)
1688 {
1689 
1690 	if (MDOC_BODY != n->type)
1691 		return;
1692 	p->flags |= TERMP_NOSPACE;
1693 	term_word(p, "\\(aq");
1694 }
1695 
1696 
1697 /* ARGSUSED */
1698 static int
1699 termp_pf_pre(DECL_ARGS)
1700 {
1701 
1702 	p->flags |= TERMP_IGNDELIM;
1703 	return(1);
1704 }
1705 
1706 
1707 /* ARGSUSED */
1708 static void
1709 termp_pf_post(DECL_ARGS)
1710 {
1711 
1712 	p->flags &= ~TERMP_IGNDELIM;
1713 	p->flags |= TERMP_NOSPACE;
1714 }
1715 
1716 
1717 /* ARGSUSED */
1718 static int
1719 termp_ss_pre(DECL_ARGS)
1720 {
1721 
1722 	switch (n->type) {
1723 	case (MDOC_BLOCK):
1724 		term_newln(p);
1725 		if (n->prev)
1726 			term_vspace(p);
1727 		break;
1728 	case (MDOC_HEAD):
1729 		p->bold++;
1730 		p->offset = HALFINDENT;
1731 		break;
1732 	default:
1733 		break;
1734 	}
1735 
1736 	return(1);
1737 }
1738 
1739 
1740 /* ARGSUSED */
1741 static void
1742 termp_ss_post(DECL_ARGS)
1743 {
1744 
1745 	if (MDOC_HEAD == n->type)
1746 		term_newln(p);
1747 }
1748 
1749 
1750 /* ARGSUSED */
1751 static int
1752 termp_cd_pre(DECL_ARGS)
1753 {
1754 
1755 	p->bold++;
1756 	term_newln(p);
1757 	return(1);
1758 }
1759 
1760 
1761 /* ARGSUSED */
1762 static int
1763 termp_in_pre(DECL_ARGS)
1764 {
1765 
1766 	p->bold++;
1767 	if (SEC_SYNOPSIS == n->sec)
1768 		term_word(p, "#include");
1769 
1770 	term_word(p, "<");
1771 	p->flags |= TERMP_NOSPACE;
1772 	return(1);
1773 }
1774 
1775 
1776 /* ARGSUSED */
1777 static void
1778 termp_in_post(DECL_ARGS)
1779 {
1780 
1781 	p->bold++;
1782 	p->flags |= TERMP_NOSPACE;
1783 	term_word(p, ">");
1784 	p->bold--;
1785 
1786 	if (SEC_SYNOPSIS != n->sec)
1787 		return;
1788 
1789 	term_newln(p);
1790 	/*
1791 	 * XXX Not entirely correct.  If `.In foo bar' is specified in
1792 	 * the SYNOPSIS section, then it produces a single break after
1793 	 * the <foo>; mandoc asserts a vertical space.  Since this
1794 	 * construction is rarely used, I think it's fine.
1795 	 */
1796 	if (n->next && MDOC_In != n->next->tok)
1797 		term_vspace(p);
1798 }
1799 
1800 
1801 /* ARGSUSED */
1802 static int
1803 termp_sp_pre(DECL_ARGS)
1804 {
1805 	size_t		 i, len;
1806 
1807 	switch (n->tok) {
1808 	case (MDOC_sp):
1809 		len = n->child ? a2height(n->child) : 1;
1810 		break;
1811 	case (MDOC_br):
1812 		len = 0;
1813 		break;
1814 	default:
1815 		len = 1;
1816 		break;
1817 	}
1818 
1819 	if (0 == len)
1820 		term_newln(p);
1821 	for (i = 0; i < len; i++)
1822 		term_vspace(p);
1823 
1824 	return(0);
1825 }
1826 
1827 
1828 /* ARGSUSED */
1829 static int
1830 termp_brq_pre(DECL_ARGS)
1831 {
1832 
1833 	if (MDOC_BODY != n->type)
1834 		return(1);
1835 	term_word(p, "\\(lC");
1836 	p->flags |= TERMP_NOSPACE;
1837 	return(1);
1838 }
1839 
1840 
1841 /* ARGSUSED */
1842 static void
1843 termp_brq_post(DECL_ARGS)
1844 {
1845 
1846 	if (MDOC_BODY != n->type)
1847 		return;
1848 	p->flags |= TERMP_NOSPACE;
1849 	term_word(p, "\\(rC");
1850 }
1851 
1852 
1853 /* ARGSUSED */
1854 static int
1855 termp_bq_pre(DECL_ARGS)
1856 {
1857 
1858 	if (MDOC_BODY != n->type)
1859 		return(1);
1860 	term_word(p, "\\(lB");
1861 	p->flags |= TERMP_NOSPACE;
1862 	return(1);
1863 }
1864 
1865 
1866 /* ARGSUSED */
1867 static void
1868 termp_bq_post(DECL_ARGS)
1869 {
1870 
1871 	if (MDOC_BODY != n->type)
1872 		return;
1873 	p->flags |= TERMP_NOSPACE;
1874 	term_word(p, "\\(rB");
1875 }
1876 
1877 
1878 /* ARGSUSED */
1879 static int
1880 termp_pq_pre(DECL_ARGS)
1881 {
1882 
1883 	if (MDOC_BODY != n->type)
1884 		return(1);
1885 	term_word(p, "\\&(");
1886 	p->flags |= TERMP_NOSPACE;
1887 	return(1);
1888 }
1889 
1890 
1891 /* ARGSUSED */
1892 static void
1893 termp_pq_post(DECL_ARGS)
1894 {
1895 
1896 	if (MDOC_BODY != n->type)
1897 		return;
1898 	term_word(p, ")");
1899 }
1900 
1901 
1902 /* ARGSUSED */
1903 static int
1904 termp_fo_pre(DECL_ARGS)
1905 {
1906 	const struct mdoc_node *nn;
1907 
1908 	if (MDOC_BODY == n->type) {
1909 		p->flags |= TERMP_NOSPACE;
1910 		term_word(p, "(");
1911 		p->flags |= TERMP_NOSPACE;
1912 		return(1);
1913 	} else if (MDOC_HEAD != n->type)
1914 		return(1);
1915 
1916 	p->bold++;
1917 	for (nn = n->child; nn; nn = nn->next) {
1918 		assert(MDOC_TEXT == nn->type);
1919 		term_word(p, nn->string);
1920 	}
1921 	p->bold--;
1922 
1923 	return(0);
1924 }
1925 
1926 
1927 /* ARGSUSED */
1928 static void
1929 termp_fo_post(DECL_ARGS)
1930 {
1931 
1932 	if (MDOC_BODY != n->type)
1933 		return;
1934 	p->flags |= TERMP_NOSPACE;
1935 	term_word(p, ")");
1936 	p->flags |= TERMP_NOSPACE;
1937 	term_word(p, ";");
1938 	term_newln(p);
1939 }
1940 
1941 
1942 /* ARGSUSED */
1943 static int
1944 termp_bf_pre(DECL_ARGS)
1945 {
1946 	const struct mdoc_node	*nn;
1947 
1948 	if (MDOC_HEAD == n->type)
1949 		return(0);
1950 	else if (MDOC_BLOCK != n->type)
1951 		return(1);
1952 
1953 	if (NULL == (nn = n->head->child)) {
1954 		if (arg_hasattr(MDOC_Emphasis, n))
1955 			p->under++;
1956 		else if (arg_hasattr(MDOC_Symbolic, n))
1957 			p->bold++;
1958 
1959 		return(1);
1960 	}
1961 
1962 	assert(MDOC_TEXT == nn->type);
1963 	if (0 == strcmp("Em", nn->string))
1964 		p->under++;
1965 	else if (0 == strcmp("Sy", nn->string))
1966 		p->bold++;
1967 
1968 	return(1);
1969 }
1970 
1971 
1972 /* ARGSUSED */
1973 static int
1974 termp_sm_pre(DECL_ARGS)
1975 {
1976 
1977 	assert(n->child && MDOC_TEXT == n->child->type);
1978 	if (0 == strcmp("on", n->child->string)) {
1979 		p->flags &= ~TERMP_NONOSPACE;
1980 		p->flags &= ~TERMP_NOSPACE;
1981 	} else
1982 		p->flags |= TERMP_NONOSPACE;
1983 
1984 	return(0);
1985 }
1986 
1987 
1988 /* ARGSUSED */
1989 static int
1990 termp_ap_pre(DECL_ARGS)
1991 {
1992 
1993 	p->flags |= TERMP_NOSPACE;
1994 	term_word(p, "\\(aq");
1995 	p->flags |= TERMP_NOSPACE;
1996 	return(1);
1997 }
1998 
1999 
2000 /* ARGSUSED */
2001 static void
2002 termp____post(DECL_ARGS)
2003 {
2004 
2005 	/* TODO: %U. */
2006 
2007 	p->flags |= TERMP_NOSPACE;
2008 	switch (n->tok) {
2009 	case (MDOC__T):
2010 		term_word(p, "\\(rq");
2011 		p->flags |= TERMP_NOSPACE;
2012 		break;
2013 	default:
2014 		break;
2015 	}
2016 	term_word(p, n->next ? "," : ".");
2017 }
2018 
2019 
2020 /* ARGSUSED */
2021 static int
2022 termp_lk_pre(DECL_ARGS)
2023 {
2024 	const struct mdoc_node *nn;
2025 
2026 	p->under++;
2027 	nn = n->child;
2028 
2029 	if (NULL == nn->next)
2030 		return(1);
2031 
2032 	term_word(p, nn->string);
2033 	p->under--;
2034 
2035 	p->flags |= TERMP_NOSPACE;
2036 	term_word(p, ":");
2037 
2038 	p->bold++;
2039 	for (nn = nn->next; nn; nn = nn->next)
2040 		term_word(p, nn->string);
2041 	p->bold--;
2042 
2043 	return(0);
2044 }
2045 
2046 
2047 /* ARGSUSED */
2048 static int
2049 termp_under_pre(DECL_ARGS)
2050 {
2051 
2052 	p->under++;
2053 	return(1);
2054 }
2055 
2056 
2057 /* ARGSUSED */
2058 static int
2059 termp__t_pre(DECL_ARGS)
2060 {
2061 
2062 	term_word(p, "\\(lq");
2063 	p->flags |= TERMP_NOSPACE;
2064 	return(1);
2065 }
2066