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