Home | History | Annotate | Line # | Download | only in dist
man_term.c revision 1.8
      1 /*	$Vendor-Id: man_term.c,v 1.121 2011/09/21 09:57:13 schwarze Exp $ */
      2 /*
      3  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps (at) bsd.lv>
      4  * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze (at) openbsd.org>
      5  *
      6  * Permission to use, copy, modify, and distribute this software for any
      7  * purpose with or without fee is hereby granted, provided that the above
      8  * copyright notice and this permission notice appear in all copies.
      9  *
     10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  */
     18 #ifdef HAVE_CONFIG_H
     19 #include "config.h"
     20 #endif
     21 
     22 #include <sys/types.h>
     23 
     24 #include <assert.h>
     25 #include <ctype.h>
     26 #include <stdio.h>
     27 #include <stdlib.h>
     28 #include <string.h>
     29 
     30 #include "mandoc.h"
     31 #include "out.h"
     32 #include "man.h"
     33 #include "term.h"
     34 #include "main.h"
     35 
     36 #define	INDENT		  7 /* fixed-width char full-indent */
     37 #define	HALFINDENT	  3 /* fixed-width char half-indent */
     38 #define	MAXMARGINS	  64 /* maximum number of indented scopes */
     39 
     40 /* FIXME: have PD set the default vspace width. */
     41 
     42 struct	mtermp {
     43 	int		  fl;
     44 #define	MANT_LITERAL	 (1 << 0)
     45 	size_t		  lmargin[MAXMARGINS]; /* margins (incl. visible page) */
     46 	int		  lmargincur; /* index of current margin */
     47 	int		  lmarginsz; /* actual number of nested margins */
     48 	size_t		  offset; /* default offset to visible page */
     49 };
     50 
     51 #define	DECL_ARGS 	  struct termp *p, \
     52 			  struct mtermp *mt, \
     53 			  const struct man_node *n, \
     54 			  const struct man_meta *m
     55 
     56 struct	termact {
     57 	int		(*pre)(DECL_ARGS);
     58 	void		(*post)(DECL_ARGS);
     59 	int		  flags;
     60 #define	MAN_NOTEXT	 (1 << 0) /* Never has text children. */
     61 };
     62 
     63 static	int		  a2width(const struct termp *, const char *);
     64 static	size_t		  a2height(const struct termp *, const char *);
     65 
     66 static	void		  print_man_nodelist(DECL_ARGS);
     67 static	void		  print_man_node(DECL_ARGS);
     68 static	void		  print_man_head(struct termp *, const void *);
     69 static	void		  print_man_foot(struct termp *, const void *);
     70 static	void		  print_bvspace(struct termp *,
     71 				const struct man_node *);
     72 
     73 static	int		  pre_alternate(DECL_ARGS);
     74 static	int		  pre_B(DECL_ARGS);
     75 static	int		  pre_HP(DECL_ARGS);
     76 static	int		  pre_I(DECL_ARGS);
     77 static	int		  pre_IP(DECL_ARGS);
     78 static	int		  pre_PP(DECL_ARGS);
     79 static	int		  pre_RS(DECL_ARGS);
     80 static	int		  pre_SH(DECL_ARGS);
     81 static	int		  pre_SS(DECL_ARGS);
     82 static	int		  pre_TP(DECL_ARGS);
     83 static	int		  pre_ign(DECL_ARGS);
     84 static	int		  pre_in(DECL_ARGS);
     85 static	int		  pre_literal(DECL_ARGS);
     86 static	int		  pre_sp(DECL_ARGS);
     87 static	int		  pre_ft(DECL_ARGS);
     88 
     89 static	void		  post_IP(DECL_ARGS);
     90 static	void		  post_HP(DECL_ARGS);
     91 static	void		  post_RS(DECL_ARGS);
     92 static	void		  post_SH(DECL_ARGS);
     93 static	void		  post_SS(DECL_ARGS);
     94 static	void		  post_TP(DECL_ARGS);
     95 
     96 static	const struct termact termacts[MAN_MAX] = {
     97 	{ pre_sp, NULL, MAN_NOTEXT }, /* br */
     98 	{ NULL, NULL, 0 }, /* TH */
     99 	{ pre_SH, post_SH, 0 }, /* SH */
    100 	{ pre_SS, post_SS, 0 }, /* SS */
    101 	{ pre_TP, post_TP, 0 }, /* TP */
    102 	{ pre_PP, NULL, 0 }, /* LP */
    103 	{ pre_PP, NULL, 0 }, /* PP */
    104 	{ pre_PP, NULL, 0 }, /* P */
    105 	{ pre_IP, post_IP, 0 }, /* IP */
    106 	{ pre_HP, post_HP, 0 }, /* HP */
    107 	{ NULL, NULL, 0 }, /* SM */
    108 	{ pre_B, NULL, 0 }, /* SB */
    109 	{ pre_alternate, NULL, 0 }, /* BI */
    110 	{ pre_alternate, NULL, 0 }, /* IB */
    111 	{ pre_alternate, NULL, 0 }, /* BR */
    112 	{ pre_alternate, NULL, 0 }, /* RB */
    113 	{ NULL, NULL, 0 }, /* R */
    114 	{ pre_B, NULL, 0 }, /* B */
    115 	{ pre_I, NULL, 0 }, /* I */
    116 	{ pre_alternate, NULL, 0 }, /* IR */
    117 	{ pre_alternate, NULL, 0 }, /* RI */
    118 	{ pre_ign, NULL, MAN_NOTEXT }, /* na */
    119 	{ pre_sp, NULL, MAN_NOTEXT }, /* sp */
    120 	{ pre_literal, NULL, 0 }, /* nf */
    121 	{ pre_literal, NULL, 0 }, /* fi */
    122 	{ NULL, NULL, 0 }, /* RE */
    123 	{ pre_RS, post_RS, 0 }, /* RS */
    124 	{ pre_ign, NULL, 0 }, /* DT */
    125 	{ pre_ign, NULL, 0 }, /* UC */
    126 	{ pre_ign, NULL, 0 }, /* PD */
    127 	{ pre_ign, NULL, 0 }, /* AT */
    128 	{ pre_in, NULL, MAN_NOTEXT }, /* in */
    129 	{ pre_ft, NULL, MAN_NOTEXT }, /* ft */
    130 };
    131 
    132 
    133 
    134 void
    135 terminal_man(void *arg, const struct man *man)
    136 {
    137 	struct termp		*p;
    138 	const struct man_node	*n;
    139 	const struct man_meta	*m;
    140 	struct mtermp		 mt;
    141 
    142 	p = (struct termp *)arg;
    143 
    144 	p->overstep = 0;
    145 	p->maxrmargin = p->defrmargin;
    146 	p->tabwidth = term_len(p, 5);
    147 
    148 	if (NULL == p->symtab)
    149 		p->symtab = mchars_alloc();
    150 
    151 	n = man_node(man);
    152 	m = man_meta(man);
    153 
    154 	term_begin(p, print_man_head, print_man_foot, m);
    155 	p->flags |= TERMP_NOSPACE;
    156 
    157 	memset(&mt, 0, sizeof(struct mtermp));
    158 
    159 	mt.lmargin[mt.lmargincur] = term_len(p, INDENT);
    160 	mt.offset = term_len(p, INDENT);
    161 
    162 	if (n->child)
    163 		print_man_nodelist(p, &mt, n->child, m);
    164 
    165 	term_end(p);
    166 }
    167 
    168 
    169 static size_t
    170 a2height(const struct termp *p, const char *cp)
    171 {
    172 	struct roffsu	 su;
    173 
    174 	if ( ! a2roffsu(cp, &su, SCALE_VS))
    175 		SCALE_VS_INIT(&su, atoi(cp));
    176 
    177 	return(term_vspan(p, &su));
    178 }
    179 
    180 
    181 static int
    182 a2width(const struct termp *p, const char *cp)
    183 {
    184 	struct roffsu	 su;
    185 
    186 	if ( ! a2roffsu(cp, &su, SCALE_BU))
    187 		return(-1);
    188 
    189 	return((int)term_hspan(p, &su));
    190 }
    191 
    192 /*
    193  * Printing leading vertical space before a block.
    194  * This is used for the paragraph macros.
    195  * The rules are pretty simple, since there's very little nesting going
    196  * on here.  Basically, if we're the first within another block (SS/SH),
    197  * then don't emit vertical space.  If we are (RS), then do.  If not the
    198  * first, print it.
    199  */
    200 static void
    201 print_bvspace(struct termp *p, const struct man_node *n)
    202 {
    203 
    204 	term_newln(p);
    205 
    206 	if (n->body && n->body->child)
    207 		if (MAN_TBL == n->body->child->type)
    208 			return;
    209 
    210 	if (MAN_ROOT == n->parent->type || MAN_RS != n->parent->tok)
    211 		if (NULL == n->prev)
    212 			return;
    213 
    214 	term_vspace(p);
    215 }
    216 
    217 /* ARGSUSED */
    218 static int
    219 pre_ign(DECL_ARGS)
    220 {
    221 
    222 	return(0);
    223 }
    224 
    225 
    226 /* ARGSUSED */
    227 static int
    228 pre_I(DECL_ARGS)
    229 {
    230 
    231 	term_fontrepl(p, TERMFONT_UNDER);
    232 	return(1);
    233 }
    234 
    235 
    236 /* ARGSUSED */
    237 static int
    238 pre_literal(DECL_ARGS)
    239 {
    240 
    241 	term_newln(p);
    242 
    243 	if (MAN_nf == n->tok)
    244 		mt->fl |= MANT_LITERAL;
    245 	else
    246 		mt->fl &= ~MANT_LITERAL;
    247 
    248 	/*
    249 	 * Unlike .IP and .TP, .HP does not have a HEAD.
    250 	 * So in case a second call to term_flushln() is needed,
    251 	 * indentation has to be set up explicitly.
    252 	 */
    253 	if (MAN_HP == n->parent->tok && p->rmargin < p->maxrmargin) {
    254 		p->offset = p->rmargin;
    255 		p->rmargin = p->maxrmargin;
    256 		p->flags &= ~(TERMP_NOBREAK | TERMP_TWOSPACE);
    257 		p->flags |= TERMP_NOSPACE;
    258 	}
    259 
    260 	return(0);
    261 }
    262 
    263 /* ARGSUSED */
    264 static int
    265 pre_alternate(DECL_ARGS)
    266 {
    267 	enum termfont		 font[2];
    268 	const struct man_node	*nn;
    269 	int			 savelit, i;
    270 
    271 	switch (n->tok) {
    272 	case (MAN_RB):
    273 		font[0] = TERMFONT_NONE;
    274 		font[1] = TERMFONT_BOLD;
    275 		break;
    276 	case (MAN_RI):
    277 		font[0] = TERMFONT_NONE;
    278 		font[1] = TERMFONT_UNDER;
    279 		break;
    280 	case (MAN_BR):
    281 		font[0] = TERMFONT_BOLD;
    282 		font[1] = TERMFONT_NONE;
    283 		break;
    284 	case (MAN_BI):
    285 		font[0] = TERMFONT_BOLD;
    286 		font[1] = TERMFONT_UNDER;
    287 		break;
    288 	case (MAN_IR):
    289 		font[0] = TERMFONT_UNDER;
    290 		font[1] = TERMFONT_NONE;
    291 		break;
    292 	case (MAN_IB):
    293 		font[0] = TERMFONT_UNDER;
    294 		font[1] = TERMFONT_BOLD;
    295 		break;
    296 	default:
    297 		abort();
    298 	}
    299 
    300 	savelit = MANT_LITERAL & mt->fl;
    301 	mt->fl &= ~MANT_LITERAL;
    302 
    303 	for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) {
    304 		term_fontrepl(p, font[i]);
    305 		if (savelit && NULL == nn->next)
    306 			mt->fl |= MANT_LITERAL;
    307 		print_man_node(p, mt, nn, m);
    308 		if (nn->next)
    309 			p->flags |= TERMP_NOSPACE;
    310 	}
    311 
    312 	return(0);
    313 }
    314 
    315 /* ARGSUSED */
    316 static int
    317 pre_B(DECL_ARGS)
    318 {
    319 
    320 	term_fontrepl(p, TERMFONT_BOLD);
    321 	return(1);
    322 }
    323 
    324 /* ARGSUSED */
    325 static int
    326 pre_ft(DECL_ARGS)
    327 {
    328 	const char	*cp;
    329 
    330 	if (NULL == n->child) {
    331 		term_fontlast(p);
    332 		return(0);
    333 	}
    334 
    335 	cp = n->child->string;
    336 	switch (*cp) {
    337 	case ('4'):
    338 		/* FALLTHROUGH */
    339 	case ('3'):
    340 		/* FALLTHROUGH */
    341 	case ('B'):
    342 		term_fontrepl(p, TERMFONT_BOLD);
    343 		break;
    344 	case ('2'):
    345 		/* FALLTHROUGH */
    346 	case ('I'):
    347 		term_fontrepl(p, TERMFONT_UNDER);
    348 		break;
    349 	case ('P'):
    350 		term_fontlast(p);
    351 		break;
    352 	case ('1'):
    353 		/* FALLTHROUGH */
    354 	case ('C'):
    355 		/* FALLTHROUGH */
    356 	case ('R'):
    357 		term_fontrepl(p, TERMFONT_NONE);
    358 		break;
    359 	default:
    360 		break;
    361 	}
    362 	return(0);
    363 }
    364 
    365 /* ARGSUSED */
    366 static int
    367 pre_in(DECL_ARGS)
    368 {
    369 	int		 len, less;
    370 	size_t		 v;
    371 	const char	*cp;
    372 
    373 	term_newln(p);
    374 
    375 	if (NULL == n->child) {
    376 		p->offset = mt->offset;
    377 		return(0);
    378 	}
    379 
    380 	cp = n->child->string;
    381 	less = 0;
    382 
    383 	if ('-' == *cp)
    384 		less = -1;
    385 	else if ('+' == *cp)
    386 		less = 1;
    387 	else
    388 		cp--;
    389 
    390 	if ((len = a2width(p, ++cp)) < 0)
    391 		return(0);
    392 
    393 	v = (size_t)len;
    394 
    395 	if (less < 0)
    396 		p->offset -= p->offset > v ? v : p->offset;
    397 	else if (less > 0)
    398 		p->offset += v;
    399 	else
    400 		p->offset = v;
    401 
    402 	/* Don't let this creep beyond the right margin. */
    403 
    404 	if (p->offset > p->rmargin)
    405 		p->offset = p->rmargin;
    406 
    407 	return(0);
    408 }
    409 
    410 
    411 /* ARGSUSED */
    412 static int
    413 pre_sp(DECL_ARGS)
    414 {
    415 	size_t		 i, len;
    416 
    417 	if ((NULL == n->prev && n->parent)) {
    418 		if (MAN_SS == n->parent->tok)
    419 			return(0);
    420 		if (MAN_SH == n->parent->tok)
    421 			return(0);
    422 	}
    423 
    424 	switch (n->tok) {
    425 	case (MAN_br):
    426 		len = 0;
    427 		break;
    428 	default:
    429 		len = n->child ? a2height(p, n->child->string) : 1;
    430 		break;
    431 	}
    432 
    433 	if (0 == len)
    434 		term_newln(p);
    435 	for (i = 0; i < len; i++)
    436 		term_vspace(p);
    437 
    438 	return(0);
    439 }
    440 
    441 
    442 /* ARGSUSED */
    443 static int
    444 pre_HP(DECL_ARGS)
    445 {
    446 	size_t			 len, one;
    447 	int			 ival;
    448 	const struct man_node	*nn;
    449 
    450 	switch (n->type) {
    451 	case (MAN_BLOCK):
    452 		print_bvspace(p, n);
    453 		return(1);
    454 	case (MAN_BODY):
    455 		p->flags |= TERMP_NOBREAK;
    456 		p->flags |= TERMP_TWOSPACE;
    457 		break;
    458 	default:
    459 		return(0);
    460 	}
    461 
    462 	len = mt->lmargin[mt->lmargincur];
    463 	ival = -1;
    464 
    465 	/* Calculate offset. */
    466 
    467 	if (NULL != (nn = n->parent->head->child))
    468 		if ((ival = a2width(p, nn->string)) >= 0)
    469 			len = (size_t)ival;
    470 
    471 	one = term_len(p, 1);
    472 	if (len < one)
    473 		len = one;
    474 
    475 	p->offset = mt->offset;
    476 	p->rmargin = mt->offset + len;
    477 
    478 	if (ival >= 0)
    479 		mt->lmargin[mt->lmargincur] = (size_t)ival;
    480 
    481 	return(1);
    482 }
    483 
    484 
    485 /* ARGSUSED */
    486 static void
    487 post_HP(DECL_ARGS)
    488 {
    489 
    490 	switch (n->type) {
    491 	case (MAN_BLOCK):
    492 		term_flushln(p);
    493 		break;
    494 	case (MAN_BODY):
    495 		term_flushln(p);
    496 		p->flags &= ~TERMP_NOBREAK;
    497 		p->flags &= ~TERMP_TWOSPACE;
    498 		p->offset = mt->offset;
    499 		p->rmargin = p->maxrmargin;
    500 		break;
    501 	default:
    502 		break;
    503 	}
    504 }
    505 
    506 
    507 /* ARGSUSED */
    508 static int
    509 pre_PP(DECL_ARGS)
    510 {
    511 
    512 	switch (n->type) {
    513 	case (MAN_BLOCK):
    514 		mt->lmargin[mt->lmargincur] = term_len(p, INDENT);
    515 		print_bvspace(p, n);
    516 		break;
    517 	default:
    518 		p->offset = mt->offset;
    519 		break;
    520 	}
    521 
    522 	return(MAN_HEAD != n->type);
    523 }
    524 
    525 
    526 /* ARGSUSED */
    527 static int
    528 pre_IP(DECL_ARGS)
    529 {
    530 	const struct man_node	*nn;
    531 	size_t			 len;
    532 	int			 savelit, ival;
    533 
    534 	switch (n->type) {
    535 	case (MAN_BODY):
    536 		p->flags |= TERMP_NOSPACE;
    537 		break;
    538 	case (MAN_HEAD):
    539 		p->flags |= TERMP_NOBREAK;
    540 		break;
    541 	case (MAN_BLOCK):
    542 		print_bvspace(p, n);
    543 		/* FALLTHROUGH */
    544 	default:
    545 		return(1);
    546 	}
    547 
    548 	len = mt->lmargin[mt->lmargincur];
    549 	ival = -1;
    550 
    551 	/* Calculate the offset from the optional second argument. */
    552 	if (NULL != (nn = n->parent->head->child))
    553 		if (NULL != (nn = nn->next))
    554 			if ((ival = a2width(p, nn->string)) >= 0)
    555 				len = (size_t)ival;
    556 
    557 	switch (n->type) {
    558 	case (MAN_HEAD):
    559 		/* Handle zero-width lengths. */
    560 		if (0 == len)
    561 			len = term_len(p, 1);
    562 
    563 		p->offset = mt->offset;
    564 		p->rmargin = mt->offset + len;
    565 		if (ival < 0)
    566 			break;
    567 
    568 		/* Set the saved left-margin. */
    569 		mt->lmargin[mt->lmargincur] = (size_t)ival;
    570 
    571 		savelit = MANT_LITERAL & mt->fl;
    572 		mt->fl &= ~MANT_LITERAL;
    573 
    574 		if (n->child)
    575 			print_man_node(p, mt, n->child, m);
    576 
    577 		if (savelit)
    578 			mt->fl |= MANT_LITERAL;
    579 
    580 		return(0);
    581 	case (MAN_BODY):
    582 		p->offset = mt->offset + len;
    583 		p->rmargin = p->maxrmargin;
    584 		break;
    585 	default:
    586 		break;
    587 	}
    588 
    589 	return(1);
    590 }
    591 
    592 
    593 /* ARGSUSED */
    594 static void
    595 post_IP(DECL_ARGS)
    596 {
    597 
    598 	switch (n->type) {
    599 	case (MAN_HEAD):
    600 		term_flushln(p);
    601 		p->flags &= ~TERMP_NOBREAK;
    602 		p->rmargin = p->maxrmargin;
    603 		break;
    604 	case (MAN_BODY):
    605 		term_newln(p);
    606 		break;
    607 	default:
    608 		break;
    609 	}
    610 }
    611 
    612 
    613 /* ARGSUSED */
    614 static int
    615 pre_TP(DECL_ARGS)
    616 {
    617 	const struct man_node	*nn;
    618 	size_t			 len;
    619 	int			 savelit, ival;
    620 
    621 	switch (n->type) {
    622 	case (MAN_HEAD):
    623 		p->flags |= TERMP_NOBREAK;
    624 		break;
    625 	case (MAN_BODY):
    626 		p->flags |= TERMP_NOSPACE;
    627 		break;
    628 	case (MAN_BLOCK):
    629 		print_bvspace(p, n);
    630 		/* FALLTHROUGH */
    631 	default:
    632 		return(1);
    633 	}
    634 
    635 	len = (size_t)mt->lmargin[mt->lmargincur];
    636 	ival = -1;
    637 
    638 	/* Calculate offset. */
    639 
    640 	if (NULL != (nn = n->parent->head->child))
    641 		if (nn->string && nn->parent->line == nn->line)
    642 			if ((ival = a2width(p, nn->string)) >= 0)
    643 				len = (size_t)ival;
    644 
    645 	switch (n->type) {
    646 	case (MAN_HEAD):
    647 		/* Handle zero-length properly. */
    648 		if (0 == len)
    649 			len = term_len(p, 1);
    650 
    651 		p->offset = mt->offset;
    652 		p->rmargin = mt->offset + len;
    653 
    654 		savelit = MANT_LITERAL & mt->fl;
    655 		mt->fl &= ~MANT_LITERAL;
    656 
    657 		/* Don't print same-line elements. */
    658 		for (nn = n->child; nn; nn = nn->next)
    659 			if (nn->line > n->line)
    660 				print_man_node(p, mt, nn, m);
    661 
    662 		if (savelit)
    663 			mt->fl |= MANT_LITERAL;
    664 		if (ival >= 0)
    665 			mt->lmargin[mt->lmargincur] = (size_t)ival;
    666 
    667 		return(0);
    668 	case (MAN_BODY):
    669 		p->offset = mt->offset + len;
    670 		p->rmargin = p->maxrmargin;
    671 		break;
    672 	default:
    673 		break;
    674 	}
    675 
    676 	return(1);
    677 }
    678 
    679 
    680 /* ARGSUSED */
    681 static void
    682 post_TP(DECL_ARGS)
    683 {
    684 
    685 	switch (n->type) {
    686 	case (MAN_HEAD):
    687 		term_flushln(p);
    688 		p->flags &= ~TERMP_NOBREAK;
    689 		p->flags &= ~TERMP_TWOSPACE;
    690 		p->rmargin = p->maxrmargin;
    691 		break;
    692 	case (MAN_BODY):
    693 		term_newln(p);
    694 		break;
    695 	default:
    696 		break;
    697 	}
    698 }
    699 
    700 
    701 /* ARGSUSED */
    702 static int
    703 pre_SS(DECL_ARGS)
    704 {
    705 
    706 	switch (n->type) {
    707 	case (MAN_BLOCK):
    708 		mt->fl &= ~MANT_LITERAL;
    709 		mt->lmargin[mt->lmargincur] = term_len(p, INDENT);
    710 		mt->offset = term_len(p, INDENT);
    711 		/* If following a prior empty `SS', no vspace. */
    712 		if (n->prev && MAN_SS == n->prev->tok)
    713 			if (NULL == n->prev->body->child)
    714 				break;
    715 		if (NULL == n->prev)
    716 			break;
    717 		term_vspace(p);
    718 		break;
    719 	case (MAN_HEAD):
    720 		term_fontrepl(p, TERMFONT_BOLD);
    721 		p->offset = term_len(p, HALFINDENT);
    722 		break;
    723 	case (MAN_BODY):
    724 		p->offset = mt->offset;
    725 		break;
    726 	default:
    727 		break;
    728 	}
    729 
    730 	return(1);
    731 }
    732 
    733 
    734 /* ARGSUSED */
    735 static void
    736 post_SS(DECL_ARGS)
    737 {
    738 
    739 	switch (n->type) {
    740 	case (MAN_HEAD):
    741 		term_newln(p);
    742 		break;
    743 	case (MAN_BODY):
    744 		term_newln(p);
    745 		break;
    746 	default:
    747 		break;
    748 	}
    749 }
    750 
    751 
    752 /* ARGSUSED */
    753 static int
    754 pre_SH(DECL_ARGS)
    755 {
    756 
    757 	switch (n->type) {
    758 	case (MAN_BLOCK):
    759 		mt->fl &= ~MANT_LITERAL;
    760 		mt->lmargin[mt->lmargincur] = term_len(p, INDENT);
    761 		mt->offset = term_len(p, INDENT);
    762 		/* If following a prior empty `SH', no vspace. */
    763 		if (n->prev && MAN_SH == n->prev->tok)
    764 			if (NULL == n->prev->body->child)
    765 				break;
    766 		/* If the first macro, no vspae. */
    767 		if (NULL == n->prev)
    768 			break;
    769 		term_vspace(p);
    770 		break;
    771 	case (MAN_HEAD):
    772 		term_fontrepl(p, TERMFONT_BOLD);
    773 		p->offset = 0;
    774 		break;
    775 	case (MAN_BODY):
    776 		p->offset = mt->offset;
    777 		break;
    778 	default:
    779 		break;
    780 	}
    781 
    782 	return(1);
    783 }
    784 
    785 
    786 /* ARGSUSED */
    787 static void
    788 post_SH(DECL_ARGS)
    789 {
    790 
    791 	switch (n->type) {
    792 	case (MAN_HEAD):
    793 		term_newln(p);
    794 		break;
    795 	case (MAN_BODY):
    796 		term_newln(p);
    797 		break;
    798 	default:
    799 		break;
    800 	}
    801 }
    802 
    803 /* ARGSUSED */
    804 static int
    805 pre_RS(DECL_ARGS)
    806 {
    807 	int		 ival;
    808 	size_t		 sz;
    809 
    810 	switch (n->type) {
    811 	case (MAN_BLOCK):
    812 		term_newln(p);
    813 		return(1);
    814 	case (MAN_HEAD):
    815 		return(0);
    816 	default:
    817 		break;
    818 	}
    819 
    820 	sz = term_len(p, INDENT);
    821 
    822 	if (NULL != (n = n->parent->head->child))
    823 		if ((ival = a2width(p, n->string)) >= 0)
    824 			sz = (size_t)ival;
    825 
    826 	mt->offset += sz;
    827 	p->rmargin = p->maxrmargin;
    828 	p->offset = mt->offset < p->rmargin ? mt->offset : p->rmargin;
    829 
    830 	if (++mt->lmarginsz < MAXMARGINS)
    831 		mt->lmargincur = mt->lmarginsz;
    832 
    833 	mt->lmargin[mt->lmargincur] = mt->lmargin[mt->lmargincur - 1];
    834 	return(1);
    835 }
    836 
    837 /* ARGSUSED */
    838 static void
    839 post_RS(DECL_ARGS)
    840 {
    841 	int		 ival;
    842 	size_t		 sz;
    843 
    844 	switch (n->type) {
    845 	case (MAN_BLOCK):
    846 		return;
    847 	case (MAN_HEAD):
    848 		return;
    849 	default:
    850 		term_newln(p);
    851 		break;
    852 	}
    853 
    854 	sz = term_len(p, INDENT);
    855 
    856 	if (NULL != (n = n->parent->head->child))
    857 		if ((ival = a2width(p, n->string)) >= 0)
    858 			sz = (size_t)ival;
    859 
    860 	mt->offset = mt->offset < sz ?  0 : mt->offset - sz;
    861 	p->offset = mt->offset;
    862 
    863 	if (--mt->lmarginsz < MAXMARGINS)
    864 		mt->lmargincur = mt->lmarginsz;
    865 }
    866 
    867 static void
    868 print_man_node(DECL_ARGS)
    869 {
    870 	size_t		 rm, rmax;
    871 	int		 c;
    872 
    873 	switch (n->type) {
    874 	case(MAN_TEXT):
    875 		/*
    876 		 * If we have a blank line, output a vertical space.
    877 		 * If we have a space as the first character, break
    878 		 * before printing the line's data.
    879 		 */
    880 		if ('\0' == *n->string) {
    881 			term_vspace(p);
    882 			return;
    883 		} else if (' ' == *n->string && MAN_LINE & n->flags)
    884 			term_newln(p);
    885 
    886 		term_word(p, n->string);
    887 
    888 		/*
    889 		 * If we're in a literal context, make sure that words
    890 		 * togehter on the same line stay together.  This is a
    891 		 * POST-printing call, so we check the NEXT word.  Since
    892 		 * -man doesn't have nested macros, we don't need to be
    893 		 * more specific than this.
    894 		 */
    895 		if (MANT_LITERAL & mt->fl && ! (TERMP_NOBREAK & p->flags) &&
    896 				(NULL == n->next ||
    897 				 n->next->line > n->line)) {
    898 			rm = p->rmargin;
    899 			rmax = p->maxrmargin;
    900 			p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
    901 			p->flags |= TERMP_NOSPACE;
    902 			term_flushln(p);
    903 			p->rmargin = rm;
    904 			p->maxrmargin = rmax;
    905 		}
    906 
    907 		if (MAN_EOS & n->flags)
    908 			p->flags |= TERMP_SENTENCE;
    909 		return;
    910 	case (MAN_EQN):
    911 		term_eqn(p, n->eqn);
    912 		return;
    913 	case (MAN_TBL):
    914 		/*
    915 		 * Tables are preceded by a newline.  Then process a
    916 		 * table line, which will cause line termination,
    917 		 */
    918 		if (TBL_SPAN_FIRST & n->span->flags)
    919 			term_newln(p);
    920 		term_tbl(p, n->span);
    921 		return;
    922 	default:
    923 		break;
    924 	}
    925 
    926 	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
    927 		term_fontrepl(p, TERMFONT_NONE);
    928 
    929 	c = 1;
    930 	if (termacts[n->tok].pre)
    931 		c = (*termacts[n->tok].pre)(p, mt, n, m);
    932 
    933 	if (c && n->child)
    934 		print_man_nodelist(p, mt, n->child, m);
    935 
    936 	if (termacts[n->tok].post)
    937 		(*termacts[n->tok].post)(p, mt, n, m);
    938 	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
    939 		term_fontrepl(p, TERMFONT_NONE);
    940 
    941 	if (MAN_EOS & n->flags)
    942 		p->flags |= TERMP_SENTENCE;
    943 }
    944 
    945 
    946 static void
    947 print_man_nodelist(DECL_ARGS)
    948 {
    949 
    950 	print_man_node(p, mt, n, m);
    951 	if ( ! n->next)
    952 		return;
    953 	print_man_nodelist(p, mt, n->next, m);
    954 }
    955 
    956 
    957 static void
    958 print_man_foot(struct termp *p, const void *arg)
    959 {
    960 	const struct man_meta *meta;
    961 
    962 	meta = (const struct man_meta *)arg;
    963 
    964 	term_fontrepl(p, TERMFONT_NONE);
    965 
    966 	term_vspace(p);
    967 	term_vspace(p);
    968 	term_vspace(p);
    969 
    970 	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
    971 	p->rmargin = p->maxrmargin - term_strlen(p, meta->date);
    972 	p->offset = 0;
    973 
    974 	/* term_strlen() can return zero. */
    975 	if (p->rmargin == p->maxrmargin)
    976 		p->rmargin--;
    977 
    978 	if (meta->source)
    979 		term_word(p, meta->source);
    980 	if (meta->source)
    981 		term_word(p, "");
    982 	term_flushln(p);
    983 
    984 	p->flags |= TERMP_NOSPACE;
    985 	p->offset = p->rmargin;
    986 	p->rmargin = p->maxrmargin;
    987 	p->flags &= ~TERMP_NOBREAK;
    988 
    989 	term_word(p, meta->date);
    990 	term_flushln(p);
    991 }
    992 
    993 
    994 static void
    995 print_man_head(struct termp *p, const void *arg)
    996 {
    997 	char		buf[BUFSIZ], title[BUFSIZ];
    998 	size_t		buflen, titlen;
    999 	const struct man_meta *m;
   1000 
   1001 	m = (const struct man_meta *)arg;
   1002 
   1003 	/*
   1004 	 * Note that old groff would spit out some spaces before the
   1005 	 * header.  We discontinue this strange behaviour, but at one
   1006 	 * point we did so here.
   1007 	 */
   1008 
   1009 	p->offset = 0;
   1010 	p->rmargin = p->maxrmargin;
   1011 
   1012 	buf[0] = title[0] = '\0';
   1013 
   1014 	if (m->vol)
   1015 		strlcpy(buf, m->vol, BUFSIZ);
   1016 	buflen = term_strlen(p, buf);
   1017 
   1018 	snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec);
   1019 	titlen = term_strlen(p, title);
   1020 
   1021 	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
   1022 	p->offset = 0;
   1023 	p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ?
   1024 	    (p->maxrmargin -
   1025 	     term_strlen(p, buf) + term_len(p, 1)) / 2 :
   1026 	    p->maxrmargin - buflen;
   1027 
   1028 	term_word(p, title);
   1029 	term_flushln(p);
   1030 
   1031 	p->flags |= TERMP_NOSPACE;
   1032 	p->offset = p->rmargin;
   1033 	p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
   1034 	    p->maxrmargin - titlen : p->maxrmargin;
   1035 
   1036 	term_word(p, buf);
   1037 	term_flushln(p);
   1038 
   1039 	p->flags &= ~TERMP_NOBREAK;
   1040 	if (p->rmargin + titlen <= p->maxrmargin) {
   1041 		p->flags |= TERMP_NOSPACE;
   1042 		p->offset = p->rmargin;
   1043 		p->rmargin = p->maxrmargin;
   1044 		term_word(p, title);
   1045 		term_flushln(p);
   1046 	}
   1047 
   1048 	p->flags &= ~TERMP_NOSPACE;
   1049 	p->offset = 0;
   1050 	p->rmargin = p->maxrmargin;
   1051 
   1052 	/*
   1053 	 * Groff likes to have some leading spaces before content.  Well
   1054 	 * that's fine by me.
   1055 	 */
   1056 
   1057 	term_vspace(p);
   1058 	term_vspace(p);
   1059 	term_vspace(p);
   1060 }
   1061