Home | History | Annotate | Line # | Download | only in dist
man_term.c revision 1.7
      1 /*	$Vendor-Id: man_term.c,v 1.105 2011/03/22 10:13:01 kristaps 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
     37 #define	HALFINDENT	  3
     38 
     39 /* FIXME: have PD set the default vspace width. */
     40 
     41 struct	mtermp {
     42 	int		  fl;
     43 #define	MANT_LITERAL	 (1 << 0)
     44 	/*
     45 	 * Default amount to indent the left margin after leading text
     46 	 * has been printed (e.g., `HP' left-indent, `TP' and `IP' body
     47 	 * indent).  This needs to be saved because `HP' and so on, if
     48 	 * not having a specified value, must default.
     49 	 *
     50 	 * Note that this is the indentation AFTER the left offset, so
     51 	 * the total offset is usually offset + lmargin.
     52 	 */
     53 	size_t		  lmargin;
     54 	/*
     55 	 * The default offset, i.e., the amount between any text and the
     56 	 * page boundary.
     57 	 */
     58 	size_t		  offset;
     59 };
     60 
     61 #define	DECL_ARGS 	  struct termp *p, \
     62 			  struct mtermp *mt, \
     63 			  const struct man_node *n, \
     64 			  const struct man_meta *m
     65 
     66 struct	termact {
     67 	int		(*pre)(DECL_ARGS);
     68 	void		(*post)(DECL_ARGS);
     69 	int		  flags;
     70 #define	MAN_NOTEXT	 (1 << 0) /* Never has text children. */
     71 };
     72 
     73 static	int		  a2width(const struct termp *, const char *);
     74 static	size_t		  a2height(const struct termp *, const char *);
     75 
     76 static	void		  print_man_nodelist(DECL_ARGS);
     77 static	void		  print_man_node(DECL_ARGS);
     78 static	void		  print_man_head(struct termp *, const void *);
     79 static	void		  print_man_foot(struct termp *, const void *);
     80 static	void		  print_bvspace(struct termp *,
     81 				const struct man_node *);
     82 
     83 static	int		  pre_alternate(DECL_ARGS);
     84 static	int		  pre_B(DECL_ARGS);
     85 static	int		  pre_HP(DECL_ARGS);
     86 static	int		  pre_I(DECL_ARGS);
     87 static	int		  pre_IP(DECL_ARGS);
     88 static	int		  pre_PP(DECL_ARGS);
     89 static	int		  pre_RS(DECL_ARGS);
     90 static	int		  pre_SH(DECL_ARGS);
     91 static	int		  pre_SS(DECL_ARGS);
     92 static	int		  pre_TP(DECL_ARGS);
     93 static	int		  pre_ign(DECL_ARGS);
     94 static	int		  pre_in(DECL_ARGS);
     95 static	int		  pre_literal(DECL_ARGS);
     96 static	int		  pre_sp(DECL_ARGS);
     97 static	int		  pre_ft(DECL_ARGS);
     98 
     99 static	void		  post_IP(DECL_ARGS);
    100 static	void		  post_HP(DECL_ARGS);
    101 static	void		  post_RS(DECL_ARGS);
    102 static	void		  post_SH(DECL_ARGS);
    103 static	void		  post_SS(DECL_ARGS);
    104 static	void		  post_TP(DECL_ARGS);
    105 
    106 static	const struct termact termacts[MAN_MAX] = {
    107 	{ pre_sp, NULL, MAN_NOTEXT }, /* br */
    108 	{ NULL, NULL, 0 }, /* TH */
    109 	{ pre_SH, post_SH, 0 }, /* SH */
    110 	{ pre_SS, post_SS, 0 }, /* SS */
    111 	{ pre_TP, post_TP, 0 }, /* TP */
    112 	{ pre_PP, NULL, 0 }, /* LP */
    113 	{ pre_PP, NULL, 0 }, /* PP */
    114 	{ pre_PP, NULL, 0 }, /* P */
    115 	{ pre_IP, post_IP, 0 }, /* IP */
    116 	{ pre_HP, post_HP, 0 }, /* HP */
    117 	{ NULL, NULL, 0 }, /* SM */
    118 	{ pre_B, NULL, 0 }, /* SB */
    119 	{ pre_alternate, NULL, 0 }, /* BI */
    120 	{ pre_alternate, NULL, 0 }, /* IB */
    121 	{ pre_alternate, NULL, 0 }, /* BR */
    122 	{ pre_alternate, NULL, 0 }, /* RB */
    123 	{ NULL, NULL, 0 }, /* R */
    124 	{ pre_B, NULL, 0 }, /* B */
    125 	{ pre_I, NULL, 0 }, /* I */
    126 	{ pre_alternate, NULL, 0 }, /* IR */
    127 	{ pre_alternate, NULL, 0 }, /* RI */
    128 	{ pre_ign, NULL, MAN_NOTEXT }, /* na */
    129 	{ pre_sp, NULL, MAN_NOTEXT }, /* sp */
    130 	{ pre_literal, NULL, 0 }, /* nf */
    131 	{ pre_literal, NULL, 0 }, /* fi */
    132 	{ NULL, NULL, 0 }, /* RE */
    133 	{ pre_RS, post_RS, 0 }, /* RS */
    134 	{ pre_ign, NULL, 0 }, /* DT */
    135 	{ pre_ign, NULL, 0 }, /* UC */
    136 	{ pre_ign, NULL, 0 }, /* PD */
    137 	{ pre_ign, NULL, 0 }, /* AT */
    138 	{ pre_in, NULL, MAN_NOTEXT }, /* in */
    139 	{ pre_ft, NULL, MAN_NOTEXT }, /* ft */
    140 };
    141 
    142 
    143 
    144 void
    145 terminal_man(void *arg, const struct man *man)
    146 {
    147 	struct termp		*p;
    148 	const struct man_node	*n;
    149 	const struct man_meta	*m;
    150 	struct mtermp		 mt;
    151 
    152 	p = (struct termp *)arg;
    153 
    154 	p->overstep = 0;
    155 	p->maxrmargin = p->defrmargin;
    156 	p->tabwidth = term_len(p, 5);
    157 
    158 	if (NULL == p->symtab)
    159 		switch (p->enc) {
    160 		case (TERMENC_ASCII):
    161 			p->symtab = chars_init(CHARS_ASCII);
    162 			break;
    163 		default:
    164 			abort();
    165 			/* NOTREACHED */
    166 		}
    167 
    168 	n = man_node(man);
    169 	m = man_meta(man);
    170 
    171 	term_begin(p, print_man_head, print_man_foot, m);
    172 	p->flags |= TERMP_NOSPACE;
    173 
    174 	mt.fl = 0;
    175 	mt.lmargin = term_len(p, INDENT);
    176 	mt.offset = term_len(p, INDENT);
    177 
    178 	if (n->child)
    179 		print_man_nodelist(p, &mt, n->child, m);
    180 
    181 	term_end(p);
    182 }
    183 
    184 
    185 static size_t
    186 a2height(const struct termp *p, const char *cp)
    187 {
    188 	struct roffsu	 su;
    189 
    190 	if ( ! a2roffsu(cp, &su, SCALE_VS))
    191 		SCALE_VS_INIT(&su, term_strlen(p, cp));
    192 
    193 	return(term_vspan(p, &su));
    194 }
    195 
    196 
    197 static int
    198 a2width(const struct termp *p, const char *cp)
    199 {
    200 	struct roffsu	 su;
    201 
    202 	if ( ! a2roffsu(cp, &su, SCALE_BU))
    203 		return(-1);
    204 
    205 	return((int)term_hspan(p, &su));
    206 }
    207 
    208 
    209 static void
    210 print_bvspace(struct termp *p, const struct man_node *n)
    211 {
    212 	term_newln(p);
    213 
    214 	if (n->body && n->body->child && MAN_TBL == n->body->child->type)
    215 		return;
    216 
    217 	if (NULL == n->prev)
    218 		return;
    219 
    220 	if (MAN_SS == n->prev->tok)
    221 		return;
    222 	if (MAN_SH == n->prev->tok)
    223 		return;
    224 
    225 	term_vspace(p);
    226 }
    227 
    228 
    229 /* ARGSUSED */
    230 static int
    231 pre_ign(DECL_ARGS)
    232 {
    233 
    234 	return(0);
    235 }
    236 
    237 
    238 /* ARGSUSED */
    239 static int
    240 pre_I(DECL_ARGS)
    241 {
    242 
    243 	term_fontrepl(p, TERMFONT_UNDER);
    244 	return(1);
    245 }
    246 
    247 
    248 /* ARGSUSED */
    249 static int
    250 pre_literal(DECL_ARGS)
    251 {
    252 
    253 	term_newln(p);
    254 
    255 	if (MAN_nf == n->tok)
    256 		mt->fl |= MANT_LITERAL;
    257 	else
    258 		mt->fl &= ~MANT_LITERAL;
    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 	switch (n->tok) {
    418 	case (MAN_br):
    419 		len = 0;
    420 		break;
    421 	default:
    422 		len = n->child ? a2height(p, n->child->string) : 1;
    423 		break;
    424 	}
    425 
    426 	if (0 == len)
    427 		term_newln(p);
    428 	for (i = 0; i < len; i++)
    429 		term_vspace(p);
    430 
    431 	return(0);
    432 }
    433 
    434 
    435 /* ARGSUSED */
    436 static int
    437 pre_HP(DECL_ARGS)
    438 {
    439 	size_t			 len;
    440 	int			 ival;
    441 	const struct man_node	*nn;
    442 
    443 	switch (n->type) {
    444 	case (MAN_BLOCK):
    445 		print_bvspace(p, n);
    446 		return(1);
    447 	case (MAN_BODY):
    448 		p->flags |= TERMP_NOBREAK;
    449 		p->flags |= TERMP_TWOSPACE;
    450 		break;
    451 	default:
    452 		return(0);
    453 	}
    454 
    455 	len = mt->lmargin;
    456 	ival = -1;
    457 
    458 	/* Calculate offset. */
    459 
    460 	if (NULL != (nn = n->parent->head->child))
    461 		if ((ival = a2width(p, nn->string)) >= 0)
    462 			len = (size_t)ival;
    463 
    464 	if (0 == len)
    465 		len = term_len(p, 1);
    466 
    467 	p->offset = mt->offset;
    468 	p->rmargin = mt->offset + len;
    469 
    470 	if (ival >= 0)
    471 		mt->lmargin = (size_t)ival;
    472 
    473 	return(1);
    474 }
    475 
    476 
    477 /* ARGSUSED */
    478 static void
    479 post_HP(DECL_ARGS)
    480 {
    481 
    482 	switch (n->type) {
    483 	case (MAN_BLOCK):
    484 		term_flushln(p);
    485 		break;
    486 	case (MAN_BODY):
    487 		term_flushln(p);
    488 		p->flags &= ~TERMP_NOBREAK;
    489 		p->flags &= ~TERMP_TWOSPACE;
    490 		p->offset = mt->offset;
    491 		p->rmargin = p->maxrmargin;
    492 		break;
    493 	default:
    494 		break;
    495 	}
    496 }
    497 
    498 
    499 /* ARGSUSED */
    500 static int
    501 pre_PP(DECL_ARGS)
    502 {
    503 
    504 	switch (n->type) {
    505 	case (MAN_BLOCK):
    506 		mt->lmargin = term_len(p, INDENT);
    507 		print_bvspace(p, n);
    508 		break;
    509 	default:
    510 		p->offset = mt->offset;
    511 		break;
    512 	}
    513 
    514 	return(MAN_HEAD != n->type);
    515 }
    516 
    517 
    518 /* ARGSUSED */
    519 static int
    520 pre_IP(DECL_ARGS)
    521 {
    522 	const struct man_node	*nn;
    523 	size_t			 len;
    524 	int			 savelit, ival;
    525 
    526 	switch (n->type) {
    527 	case (MAN_BODY):
    528 		p->flags |= TERMP_NOLPAD;
    529 		p->flags |= TERMP_NOSPACE;
    530 		break;
    531 	case (MAN_HEAD):
    532 		p->flags |= TERMP_NOBREAK;
    533 		break;
    534 	case (MAN_BLOCK):
    535 		print_bvspace(p, n);
    536 		/* FALLTHROUGH */
    537 	default:
    538 		return(1);
    539 	}
    540 
    541 	len = mt->lmargin;
    542 	ival = -1;
    543 
    544 	/* Calculate the offset from the optional second argument. */
    545 	if (NULL != (nn = n->parent->head->child))
    546 		if (NULL != (nn = nn->next))
    547 			if ((ival = a2width(p, nn->string)) >= 0)
    548 				len = (size_t)ival;
    549 
    550 	switch (n->type) {
    551 	case (MAN_HEAD):
    552 		/* Handle zero-width lengths. */
    553 		if (0 == len)
    554 			len = term_len(p, 1);
    555 
    556 		p->offset = mt->offset;
    557 		p->rmargin = mt->offset + len;
    558 		if (ival < 0)
    559 			break;
    560 
    561 		/* Set the saved left-margin. */
    562 		mt->lmargin = (size_t)ival;
    563 
    564 		savelit = MANT_LITERAL & mt->fl;
    565 		mt->fl &= ~MANT_LITERAL;
    566 
    567 		if (n->child)
    568 			print_man_node(p, mt, n->child, m);
    569 
    570 		if (savelit)
    571 			mt->fl |= MANT_LITERAL;
    572 
    573 		return(0);
    574 	case (MAN_BODY):
    575 		p->offset = mt->offset + len;
    576 		p->rmargin = p->maxrmargin;
    577 		break;
    578 	default:
    579 		break;
    580 	}
    581 
    582 	return(1);
    583 }
    584 
    585 
    586 /* ARGSUSED */
    587 static void
    588 post_IP(DECL_ARGS)
    589 {
    590 
    591 	switch (n->type) {
    592 	case (MAN_HEAD):
    593 		term_flushln(p);
    594 		p->flags &= ~TERMP_NOBREAK;
    595 		p->rmargin = p->maxrmargin;
    596 		break;
    597 	case (MAN_BODY):
    598 		term_newln(p);
    599 		p->flags &= ~TERMP_NOLPAD;
    600 		break;
    601 	default:
    602 		break;
    603 	}
    604 }
    605 
    606 
    607 /* ARGSUSED */
    608 static int
    609 pre_TP(DECL_ARGS)
    610 {
    611 	const struct man_node	*nn;
    612 	size_t			 len;
    613 	int			 savelit, ival;
    614 
    615 	switch (n->type) {
    616 	case (MAN_HEAD):
    617 		p->flags |= TERMP_NOBREAK;
    618 		break;
    619 	case (MAN_BODY):
    620 		p->flags |= TERMP_NOLPAD;
    621 		p->flags |= TERMP_NOSPACE;
    622 		break;
    623 	case (MAN_BLOCK):
    624 		print_bvspace(p, n);
    625 		/* FALLTHROUGH */
    626 	default:
    627 		return(1);
    628 	}
    629 
    630 	len = (size_t)mt->lmargin;
    631 	ival = -1;
    632 
    633 	/* Calculate offset. */
    634 
    635 	if (NULL != (nn = n->parent->head->child)) {
    636 		while (nn && MAN_TEXT != nn->type)
    637 			nn = nn->next;
    638 		if (nn && nn->next)
    639 			if ((ival = a2width(p, nn->string)) >= 0)
    640 				len = (size_t)ival;
    641 	}
    642 
    643 	switch (n->type) {
    644 	case (MAN_HEAD):
    645 		/* Handle zero-length properly. */
    646 		if (0 == len)
    647 			len = term_len(p, 1);
    648 
    649 		p->offset = mt->offset;
    650 		p->rmargin = mt->offset + len;
    651 
    652 		savelit = MANT_LITERAL & mt->fl;
    653 		mt->fl &= ~MANT_LITERAL;
    654 
    655 		/* Don't print same-line elements. */
    656 		for (nn = n->child; nn; nn = nn->next)
    657 			if (nn->line > n->line)
    658 				print_man_node(p, mt, nn, m);
    659 
    660 		if (savelit)
    661 			mt->fl |= MANT_LITERAL;
    662 
    663 		if (ival >= 0)
    664 			mt->lmargin = (size_t)ival;
    665 
    666 		return(0);
    667 	case (MAN_BODY):
    668 		p->offset = mt->offset + len;
    669 		p->rmargin = p->maxrmargin;
    670 		break;
    671 	default:
    672 		break;
    673 	}
    674 
    675 	return(1);
    676 }
    677 
    678 
    679 /* ARGSUSED */
    680 static void
    681 post_TP(DECL_ARGS)
    682 {
    683 
    684 	switch (n->type) {
    685 	case (MAN_HEAD):
    686 		term_flushln(p);
    687 		p->flags &= ~TERMP_NOBREAK;
    688 		p->flags &= ~TERMP_TWOSPACE;
    689 		p->rmargin = p->maxrmargin;
    690 		break;
    691 	case (MAN_BODY):
    692 		term_newln(p);
    693 		p->flags &= ~TERMP_NOLPAD;
    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->lmargin = term_len(p, INDENT);
    709 		mt->offset = term_len(p, INDENT);
    710 		/* If following a prior empty `SS', no vspace. */
    711 		if (n->prev && MAN_SS == n->prev->tok)
    712 			if (NULL == n->prev->body->child)
    713 				break;
    714 		if (NULL == n->prev)
    715 			break;
    716 		term_vspace(p);
    717 		break;
    718 	case (MAN_HEAD):
    719 		term_fontrepl(p, TERMFONT_BOLD);
    720 		p->offset = term_len(p, HALFINDENT);
    721 		break;
    722 	case (MAN_BODY):
    723 		p->offset = mt->offset;
    724 		break;
    725 	default:
    726 		break;
    727 	}
    728 
    729 	return(1);
    730 }
    731 
    732 
    733 /* ARGSUSED */
    734 static void
    735 post_SS(DECL_ARGS)
    736 {
    737 
    738 	switch (n->type) {
    739 	case (MAN_HEAD):
    740 		term_newln(p);
    741 		break;
    742 	case (MAN_BODY):
    743 		term_newln(p);
    744 		break;
    745 	default:
    746 		break;
    747 	}
    748 }
    749 
    750 
    751 /* ARGSUSED */
    752 static int
    753 pre_SH(DECL_ARGS)
    754 {
    755 
    756 	switch (n->type) {
    757 	case (MAN_BLOCK):
    758 		mt->lmargin = term_len(p, INDENT);
    759 		mt->offset = term_len(p, INDENT);
    760 		/* If following a prior empty `SH', no vspace. */
    761 		if (n->prev && MAN_SH == n->prev->tok)
    762 			if (NULL == n->prev->body->child)
    763 				break;
    764 		/* If the first macro, no vspae. */
    765 		if (NULL == n->prev)
    766 			break;
    767 		term_vspace(p);
    768 		break;
    769 	case (MAN_HEAD):
    770 		term_fontrepl(p, TERMFONT_BOLD);
    771 		p->offset = 0;
    772 		break;
    773 	case (MAN_BODY):
    774 		p->offset = mt->offset;
    775 		break;
    776 	default:
    777 		break;
    778 	}
    779 
    780 	return(1);
    781 }
    782 
    783 
    784 /* ARGSUSED */
    785 static void
    786 post_SH(DECL_ARGS)
    787 {
    788 
    789 	switch (n->type) {
    790 	case (MAN_HEAD):
    791 		term_newln(p);
    792 		break;
    793 	case (MAN_BODY):
    794 		term_newln(p);
    795 		break;
    796 	default:
    797 		break;
    798 	}
    799 }
    800 
    801 
    802 /* ARGSUSED */
    803 static int
    804 pre_RS(DECL_ARGS)
    805 {
    806 	const struct man_node	*nn;
    807 	int			 ival;
    808 
    809 	switch (n->type) {
    810 	case (MAN_BLOCK):
    811 		term_newln(p);
    812 		return(1);
    813 	case (MAN_HEAD):
    814 		return(0);
    815 	default:
    816 		break;
    817 	}
    818 
    819 	if (NULL == (nn = n->parent->head->child)) {
    820 		mt->offset = mt->lmargin + term_len(p, INDENT);
    821 		p->offset = mt->offset;
    822 		return(1);
    823 	}
    824 
    825 	if ((ival = a2width(p, nn->string)) < 0)
    826 		return(1);
    827 
    828 	mt->offset = term_len(p, INDENT) + (size_t)ival;
    829 	p->offset = mt->offset;
    830 
    831 	return(1);
    832 }
    833 
    834 
    835 /* ARGSUSED */
    836 static void
    837 post_RS(DECL_ARGS)
    838 {
    839 
    840 	switch (n->type) {
    841 	case (MAN_BLOCK):
    842 		mt->offset = mt->lmargin = term_len(p, INDENT);
    843 		break;
    844 	case (MAN_HEAD):
    845 		break;
    846 	default:
    847 		term_newln(p);
    848 		p->offset = term_len(p, INDENT);
    849 		break;
    850 	}
    851 }
    852 
    853 
    854 static void
    855 print_man_node(DECL_ARGS)
    856 {
    857 	size_t		 rm, rmax;
    858 	int		 c;
    859 
    860 	switch (n->type) {
    861 	case(MAN_TEXT):
    862 		/*
    863 		 * If we have a blank line, output a vertical space.
    864 		 * If we have a space as the first character, break
    865 		 * before printing the line's data.
    866 		 */
    867 		if ('\0' == *n->string) {
    868 			term_vspace(p);
    869 			return;
    870 		} else if (' ' == *n->string && MAN_LINE & n->flags)
    871 			term_newln(p);
    872 
    873 		term_word(p, n->string);
    874 
    875 		/*
    876 		 * If we're in a literal context, make sure that words
    877 		 * togehter on the same line stay together.  This is a
    878 		 * POST-printing call, so we check the NEXT word.  Since
    879 		 * -man doesn't have nested macros, we don't need to be
    880 		 * more specific than this.
    881 		 */
    882 		if (MANT_LITERAL & mt->fl &&
    883 				(NULL == n->next ||
    884 				 n->next->line > n->line)) {
    885 			rm = p->rmargin;
    886 			rmax = p->maxrmargin;
    887 			p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
    888 			p->flags |= TERMP_NOSPACE;
    889 			term_flushln(p);
    890 			p->flags &= ~TERMP_NOLPAD;
    891 			p->rmargin = rm;
    892 			p->maxrmargin = rmax;
    893 		}
    894 
    895 		if (MAN_EOS & n->flags)
    896 			p->flags |= TERMP_SENTENCE;
    897 		return;
    898 	case (MAN_EQN):
    899 		term_word(p, n->eqn->data);
    900 		return;
    901 	case (MAN_TBL):
    902 		/*
    903 		 * Tables are preceded by a newline.  Then process a
    904 		 * table line, which will cause line termination,
    905 		 */
    906 		if (TBL_SPAN_FIRST & n->span->flags)
    907 			term_newln(p);
    908 		term_tbl(p, n->span);
    909 		return;
    910 	default:
    911 		break;
    912 	}
    913 
    914 	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
    915 		term_fontrepl(p, TERMFONT_NONE);
    916 
    917 	c = 1;
    918 	if (termacts[n->tok].pre)
    919 		c = (*termacts[n->tok].pre)(p, mt, n, m);
    920 
    921 	if (c && n->child)
    922 		print_man_nodelist(p, mt, n->child, m);
    923 
    924 	if (termacts[n->tok].post)
    925 		(*termacts[n->tok].post)(p, mt, n, m);
    926 	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
    927 		term_fontrepl(p, TERMFONT_NONE);
    928 
    929 	if (MAN_EOS & n->flags)
    930 		p->flags |= TERMP_SENTENCE;
    931 }
    932 
    933 
    934 static void
    935 print_man_nodelist(DECL_ARGS)
    936 {
    937 
    938 	print_man_node(p, mt, n, m);
    939 	if ( ! n->next)
    940 		return;
    941 	print_man_nodelist(p, mt, n->next, m);
    942 }
    943 
    944 
    945 static void
    946 print_man_foot(struct termp *p, const void *arg)
    947 {
    948 	const struct man_meta *meta;
    949 
    950 	meta = (const struct man_meta *)arg;
    951 
    952 	term_fontrepl(p, TERMFONT_NONE);
    953 
    954 	term_vspace(p);
    955 	term_vspace(p);
    956 	term_vspace(p);
    957 
    958 	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
    959 	p->rmargin = p->maxrmargin - term_strlen(p, meta->date);
    960 	p->offset = 0;
    961 
    962 	/* term_strlen() can return zero. */
    963 	if (p->rmargin == p->maxrmargin)
    964 		p->rmargin--;
    965 
    966 	if (meta->source)
    967 		term_word(p, meta->source);
    968 	if (meta->source)
    969 		term_word(p, "");
    970 	term_flushln(p);
    971 
    972 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
    973 	p->offset = p->rmargin;
    974 	p->rmargin = p->maxrmargin;
    975 	p->flags &= ~TERMP_NOBREAK;
    976 
    977 	term_word(p, meta->date);
    978 	term_flushln(p);
    979 }
    980 
    981 
    982 static void
    983 print_man_head(struct termp *p, const void *arg)
    984 {
    985 	char		buf[BUFSIZ], title[BUFSIZ];
    986 	size_t		buflen, titlen;
    987 	const struct man_meta *m;
    988 
    989 	m = (const struct man_meta *)arg;
    990 
    991 	/*
    992 	 * Note that old groff would spit out some spaces before the
    993 	 * header.  We discontinue this strange behaviour, but at one
    994 	 * point we did so here.
    995 	 */
    996 
    997 	p->rmargin = p->maxrmargin;
    998 
    999 	p->offset = 0;
   1000 	buf[0] = title[0] = '\0';
   1001 
   1002 	if (m->vol)
   1003 		strlcpy(buf, m->vol, BUFSIZ);
   1004 	buflen = term_strlen(p, buf);
   1005 
   1006 	snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec);
   1007 	titlen = term_strlen(p, title);
   1008 
   1009 	p->offset = 0;
   1010 	p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ?
   1011 	    (p->maxrmargin -
   1012 	     term_strlen(p, buf) + term_len(p, 1)) / 2 :
   1013 	    p->maxrmargin - buflen;
   1014 	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
   1015 
   1016 	term_word(p, title);
   1017 	term_flushln(p);
   1018 
   1019 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
   1020 	p->offset = p->rmargin;
   1021 	p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
   1022 	    p->maxrmargin - titlen : p->maxrmargin;
   1023 
   1024 	term_word(p, buf);
   1025 	term_flushln(p);
   1026 
   1027 	p->flags &= ~TERMP_NOBREAK;
   1028 	if (p->rmargin + titlen <= p->maxrmargin) {
   1029 		p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
   1030 		p->offset = p->rmargin;
   1031 		p->rmargin = p->maxrmargin;
   1032 		term_word(p, title);
   1033 		term_flushln(p);
   1034 	}
   1035 
   1036 	p->rmargin = p->maxrmargin;
   1037 	p->offset = 0;
   1038 	p->flags &= ~TERMP_NOSPACE;
   1039 
   1040 	/*
   1041 	 * Groff likes to have some leading spaces before content.  Well
   1042 	 * that's fine by me.
   1043 	 */
   1044 
   1045 	term_vspace(p);
   1046 	term_vspace(p);
   1047 	term_vspace(p);
   1048 }
   1049