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