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