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