Home | History | Annotate | Line # | Download | only in dist
man_term.c revision 1.1.1.1
      1 /*	$Vendor-Id: man_term.c,v 1.41 2009/10/18 19:17:14 kristaps Exp $ */
      2 /*
      3  * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps (at) kth.se>
      4  *
      5  * Permission to use, copy, modify, and distribute this software for any
      6  * purpose with or without fee is hereby granted, provided that the above
      7  * copyright notice and this permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     16  */
     17 #include <sys/types.h>
     18 
     19 #include <assert.h>
     20 #include <ctype.h>
     21 #include <err.h>
     22 #include <stdio.h>
     23 #include <stdlib.h>
     24 #include <string.h>
     25 
     26 #include "out.h"
     27 #include "man.h"
     28 #include "term.h"
     29 #include "chars.h"
     30 #include "main.h"
     31 
     32 #define	INDENT		  7
     33 #define	HALFINDENT	  3
     34 
     35 struct	mtermp {
     36 	int		  fl;
     37 #define	MANT_LITERAL	 (1 << 0)
     38 	/*
     39 	 * Default amount to indent the left margin after leading text
     40 	 * has been printed (e.g., `HP' left-indent, `TP' and `IP' body
     41 	 * indent).  This needs to be saved because `HP' and so on, if
     42 	 * not having a specified value, must default.
     43 	 *
     44 	 * Note that this is the indentation AFTER the left offset, so
     45 	 * the total offset is usually offset + lmargin.
     46 	 */
     47 	size_t		  lmargin;
     48 	/*
     49 	 * The default offset, i.e., the amount between any text and the
     50 	 * page boundary.
     51 	 */
     52 	size_t		  offset;
     53 };
     54 
     55 #define	DECL_ARGS 	  struct termp *p, \
     56 			  struct mtermp *mt, \
     57 			  const struct man_node *n, \
     58 			  const struct man_meta *m
     59 
     60 struct	termact {
     61 	int		(*pre)(DECL_ARGS);
     62 	void		(*post)(DECL_ARGS);
     63 };
     64 
     65 #ifdef __linux__
     66 extern	size_t		  strlcpy(char *, const char *, size_t);
     67 extern	size_t		  strlcat(char *, const char *, size_t);
     68 #endif
     69 
     70 static	int		  arg2width(const struct man_node *);
     71 static	int		  arg2height(const struct man_node *);
     72 
     73 static	void		  print_head(struct termp *,
     74 				const struct man_meta *);
     75 static	void		  print_body(DECL_ARGS);
     76 static	void		  print_node(DECL_ARGS);
     77 static	void		  print_foot(struct termp *,
     78 				const struct man_meta *);
     79 static	void		  print_bvspace(struct termp *,
     80 				const struct man_node *);
     81 
     82 static	int		  pre_B(DECL_ARGS);
     83 static	int		  pre_BI(DECL_ARGS);
     84 static	int		  pre_HP(DECL_ARGS);
     85 static	int		  pre_I(DECL_ARGS);
     86 static	int		  pre_IP(DECL_ARGS);
     87 static	int		  pre_IR(DECL_ARGS);
     88 static	int		  pre_PP(DECL_ARGS);
     89 static	int		  pre_RB(DECL_ARGS);
     90 static	int		  pre_RI(DECL_ARGS);
     91 static	int		  pre_RS(DECL_ARGS);
     92 static	int		  pre_SH(DECL_ARGS);
     93 static	int		  pre_SS(DECL_ARGS);
     94 static	int		  pre_TP(DECL_ARGS);
     95 static	int		  pre_br(DECL_ARGS);
     96 static	int		  pre_fi(DECL_ARGS);
     97 static	int		  pre_ign(DECL_ARGS);
     98 static	int		  pre_nf(DECL_ARGS);
     99 static	int		  pre_r(DECL_ARGS);
    100 static	int		  pre_sp(DECL_ARGS);
    101 
    102 static	void		  post_B(DECL_ARGS);
    103 static	void		  post_I(DECL_ARGS);
    104 static	void		  post_IP(DECL_ARGS);
    105 static	void		  post_HP(DECL_ARGS);
    106 static	void		  post_RS(DECL_ARGS);
    107 static	void		  post_SH(DECL_ARGS);
    108 static	void		  post_SS(DECL_ARGS);
    109 static	void		  post_TP(DECL_ARGS);
    110 static	void		  post_i(DECL_ARGS);
    111 
    112 static	const struct termact termacts[MAN_MAX] = {
    113 	{ pre_br, NULL }, /* br */
    114 	{ NULL, NULL }, /* TH */
    115 	{ pre_SH, post_SH }, /* SH */
    116 	{ pre_SS, post_SS }, /* SS */
    117 	{ pre_TP, post_TP }, /* TP */
    118 	{ pre_PP, NULL }, /* LP */
    119 	{ pre_PP, NULL }, /* PP */
    120 	{ pre_PP, NULL }, /* P */
    121 	{ pre_IP, post_IP }, /* IP */
    122 	{ pre_HP, post_HP }, /* HP */
    123 	{ NULL, NULL }, /* SM */
    124 	{ pre_B, post_B }, /* SB */
    125 	{ pre_BI, NULL }, /* BI */
    126 	{ pre_BI, NULL }, /* IB */
    127 	{ pre_RB, NULL }, /* BR */
    128 	{ pre_RB, NULL }, /* RB */
    129 	{ NULL, NULL }, /* R */
    130 	{ pre_B, post_B }, /* B */
    131 	{ pre_I, post_I }, /* I */
    132 	{ pre_IR, NULL }, /* IR */
    133 	{ pre_RI, NULL }, /* RI */
    134 	{ NULL, NULL }, /* na */
    135 	{ pre_I, post_i }, /* i */
    136 	{ pre_sp, NULL }, /* sp */
    137 	{ pre_nf, NULL }, /* nf */
    138 	{ pre_fi, NULL }, /* fi */
    139 	{ pre_r, NULL }, /* r */
    140 	{ NULL, NULL }, /* RE */
    141 	{ pre_RS, post_RS }, /* RS */
    142 	{ pre_ign, NULL }, /* DT */
    143 	{ pre_ign, NULL }, /* UC */
    144 };
    145 
    146 
    147 
    148 void
    149 terminal_man(void *arg, const struct man *man)
    150 {
    151 	struct termp		*p;
    152 	const struct man_node	*n;
    153 	const struct man_meta	*m;
    154 	struct mtermp		 mt;
    155 
    156 	p = (struct termp *)arg;
    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 	print_head(p, m);
    172 	p->flags |= TERMP_NOSPACE;
    173 
    174 	mt.fl = 0;
    175 	mt.lmargin = INDENT;
    176 	mt.offset = INDENT;
    177 
    178 	if (n->child)
    179 		print_body(p, &mt, n->child, m);
    180 	print_foot(p, m);
    181 }
    182 
    183 
    184 static int
    185 arg2height(const struct man_node *n)
    186 {
    187 	struct roffsu	 su;
    188 
    189 	assert(MAN_TEXT == n->type);
    190 	assert(n->string);
    191 	if ( ! a2roffsu(n->string, &su, SCALE_VS))
    192 		SCALE_VS_INIT(&su, strlen(n->string));
    193 
    194 	return((int)term_vspan(&su));
    195 }
    196 
    197 
    198 static int
    199 arg2width(const struct man_node *n)
    200 {
    201 	struct roffsu	 su;
    202 
    203 	assert(MAN_TEXT == n->type);
    204 	assert(n->string);
    205 	if ( ! a2roffsu(n->string, &su, SCALE_BU))
    206 		return(-1);
    207 
    208 	return((int)term_hspan(&su));
    209 }
    210 
    211 
    212 static void
    213 print_bvspace(struct termp *p, const struct man_node *n)
    214 {
    215 	term_newln(p);
    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 	p->under++;
    244 	return(1);
    245 }
    246 
    247 
    248 /* ARGSUSED */
    249 static int
    250 pre_r(DECL_ARGS)
    251 {
    252 
    253 	p->bold = p->under = 0;
    254 	return(1);
    255 }
    256 
    257 
    258 /* ARGSUSED */
    259 static void
    260 post_i(DECL_ARGS)
    261 {
    262 
    263 	if (n->nchild)
    264 		p->under--;
    265 }
    266 
    267 
    268 /* ARGSUSED */
    269 static void
    270 post_I(DECL_ARGS)
    271 {
    272 
    273 	p->under--;
    274 }
    275 
    276 
    277 /* ARGSUSED */
    278 static int
    279 pre_fi(DECL_ARGS)
    280 {
    281 
    282 	mt->fl &= ~MANT_LITERAL;
    283 	return(1);
    284 }
    285 
    286 
    287 /* ARGSUSED */
    288 static int
    289 pre_nf(DECL_ARGS)
    290 {
    291 
    292 	term_newln(p);
    293 	mt->fl |= MANT_LITERAL;
    294 	return(1);
    295 }
    296 
    297 
    298 /* ARGSUSED */
    299 static int
    300 pre_IR(DECL_ARGS)
    301 {
    302 	const struct man_node *nn;
    303 	int		 i;
    304 
    305 	for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
    306 		if ( ! (i % 2))
    307 			p->under++;
    308 		if (i > 0)
    309 			p->flags |= TERMP_NOSPACE;
    310 		print_node(p, mt, nn, m);
    311 		if ( ! (i % 2))
    312 			p->under--;
    313 	}
    314 	return(0);
    315 }
    316 
    317 
    318 /* ARGSUSED */
    319 static int
    320 pre_RB(DECL_ARGS)
    321 {
    322 	const struct man_node *nn;
    323 	int		 i;
    324 
    325 	for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
    326 		if (i % 2 && MAN_RB == n->tok)
    327 			p->bold++;
    328 		else if ( ! (i % 2) && MAN_RB != n->tok)
    329 			p->bold++;
    330 
    331 		if (i > 0)
    332 			p->flags |= TERMP_NOSPACE;
    333 
    334 		print_node(p, mt, nn, m);
    335 
    336 		if (i % 2 && MAN_RB == n->tok)
    337 			p->bold--;
    338 		else if ( ! (i % 2) && MAN_RB != n->tok)
    339 			p->bold--;
    340 	}
    341 	return(0);
    342 }
    343 
    344 
    345 /* ARGSUSED */
    346 static int
    347 pre_RI(DECL_ARGS)
    348 {
    349 	const struct man_node *nn;
    350 	int		 i;
    351 
    352 	for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
    353 		if ( ! (i % 2))
    354 			p->under++;
    355 		if (i > 0)
    356 			p->flags |= TERMP_NOSPACE;
    357 		print_node(p, mt, nn, m);
    358 		if ( ! (i % 2))
    359 			p->under--;
    360 	}
    361 	return(0);
    362 }
    363 
    364 
    365 /* ARGSUSED */
    366 static int
    367 pre_BI(DECL_ARGS)
    368 {
    369 	const struct man_node	*nn;
    370 	int			 i;
    371 
    372 	for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
    373 		if (i % 2 && MAN_BI == n->tok)
    374 			p->under++;
    375 		else if (i % 2)
    376 			p->bold++;
    377 		else if (MAN_BI == n->tok)
    378 			p->bold++;
    379 		else
    380 			p->under++;
    381 
    382 		if (i)
    383 			p->flags |= TERMP_NOSPACE;
    384 		print_node(p, mt, nn, m);
    385 
    386 		if (i % 2 && MAN_BI == n->tok)
    387 			p->under--;
    388 		else if (i % 2)
    389 			p->bold--;
    390 		else if (MAN_BI == n->tok)
    391 			p->bold--;
    392 		else
    393 			p->under--;
    394 	}
    395 	return(0);
    396 }
    397 
    398 
    399 /* ARGSUSED */
    400 static int
    401 pre_B(DECL_ARGS)
    402 {
    403 
    404 	p->bold++;
    405 	return(1);
    406 }
    407 
    408 
    409 /* ARGSUSED */
    410 static void
    411 post_B(DECL_ARGS)
    412 {
    413 
    414 	p->bold--;
    415 }
    416 
    417 
    418 /* ARGSUSED */
    419 static int
    420 pre_sp(DECL_ARGS)
    421 {
    422 	int		 i, len;
    423 
    424 	len = n->child ? arg2height(n->child) : 1;
    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_br(DECL_ARGS)
    438 {
    439 
    440 	term_newln(p);
    441 	return(0);
    442 }
    443 
    444 
    445 /* ARGSUSED */
    446 static int
    447 pre_HP(DECL_ARGS)
    448 {
    449 	size_t			 len;
    450 	int			 ival;
    451 	const struct man_node	*nn;
    452 
    453 	switch (n->type) {
    454 	case (MAN_BLOCK):
    455 		print_bvspace(p, n);
    456 		return(1);
    457 	case (MAN_BODY):
    458 		p->flags |= TERMP_NOBREAK;
    459 		p->flags |= TERMP_TWOSPACE;
    460 		break;
    461 	default:
    462 		return(0);
    463 	}
    464 
    465 	len = mt->lmargin;
    466 	ival = -1;
    467 
    468 	/* Calculate offset. */
    469 
    470 	if (NULL != (nn = n->parent->head->child))
    471 		if ((ival = arg2width(nn)) >= 0)
    472 			len = (size_t)ival;
    473 
    474 	if (0 == len)
    475 		len = 1;
    476 
    477 	p->offset = mt->offset;
    478 	p->rmargin = mt->offset + len;
    479 
    480 	if (ival >= 0)
    481 		mt->lmargin = (size_t)ival;
    482 
    483 	return(1);
    484 }
    485 
    486 
    487 /* ARGSUSED */
    488 static void
    489 post_HP(DECL_ARGS)
    490 {
    491 
    492 	switch (n->type) {
    493 	case (MAN_BLOCK):
    494 		term_flushln(p);
    495 		break;
    496 	case (MAN_BODY):
    497 		term_flushln(p);
    498 		p->flags &= ~TERMP_NOBREAK;
    499 		p->flags &= ~TERMP_TWOSPACE;
    500 		p->offset = mt->offset;
    501 		p->rmargin = p->maxrmargin;
    502 		break;
    503 	default:
    504 		break;
    505 	}
    506 }
    507 
    508 
    509 /* ARGSUSED */
    510 static int
    511 pre_PP(DECL_ARGS)
    512 {
    513 
    514 	switch (n->type) {
    515 	case (MAN_BLOCK):
    516 		mt->lmargin = INDENT;
    517 		print_bvspace(p, n);
    518 		break;
    519 	default:
    520 		p->offset = mt->offset;
    521 		break;
    522 	}
    523 
    524 	return(1);
    525 }
    526 
    527 
    528 /* ARGSUSED */
    529 static int
    530 pre_IP(DECL_ARGS)
    531 {
    532 	const struct man_node	*nn;
    533 	size_t			 len;
    534 	int			 ival;
    535 
    536 	switch (n->type) {
    537 	case (MAN_BODY):
    538 		p->flags |= TERMP_NOLPAD;
    539 		p->flags |= TERMP_NOSPACE;
    540 		break;
    541 	case (MAN_HEAD):
    542 		p->flags |= TERMP_NOBREAK;
    543 		p->flags |= TERMP_TWOSPACE;
    544 		break;
    545 	case (MAN_BLOCK):
    546 		print_bvspace(p, n);
    547 		/* FALLTHROUGH */
    548 	default:
    549 		return(1);
    550 	}
    551 
    552 	len = mt->lmargin;
    553 	ival = -1;
    554 
    555 	/* Calculate offset. */
    556 
    557 	if (NULL != (nn = n->parent->head->child))
    558 		if (NULL != (nn = nn->next)) {
    559 			for ( ; nn->next; nn = nn->next)
    560 				/* Do nothing. */ ;
    561 			if ((ival = arg2width(nn)) >= 0)
    562 				len = (size_t)ival;
    563 		}
    564 
    565 	switch (n->type) {
    566 	case (MAN_HEAD):
    567 		/* Handle zero-width lengths. */
    568 		if (0 == len)
    569 			len = 1;
    570 
    571 		p->offset = mt->offset;
    572 		p->rmargin = mt->offset + len;
    573 		if (ival < 0)
    574 			break;
    575 
    576 		/* Set the saved left-margin. */
    577 		mt->lmargin = (size_t)ival;
    578 
    579 		/* Don't print the length value. */
    580 		for (nn = n->child; nn->next; nn = nn->next)
    581 			print_node(p, mt, nn, m);
    582 		return(0);
    583 	case (MAN_BODY):
    584 		p->offset = mt->offset + len;
    585 		p->rmargin = p->maxrmargin;
    586 		break;
    587 	default:
    588 		break;
    589 	}
    590 
    591 	return(1);
    592 }
    593 
    594 
    595 /* ARGSUSED */
    596 static void
    597 post_IP(DECL_ARGS)
    598 {
    599 
    600 	switch (n->type) {
    601 	case (MAN_HEAD):
    602 		term_flushln(p);
    603 		p->flags &= ~TERMP_NOBREAK;
    604 		p->flags &= ~TERMP_TWOSPACE;
    605 		p->rmargin = p->maxrmargin;
    606 		break;
    607 	case (MAN_BODY):
    608 		term_flushln(p);
    609 		p->flags &= ~TERMP_NOLPAD;
    610 		break;
    611 	default:
    612 		break;
    613 	}
    614 }
    615 
    616 
    617 /* ARGSUSED */
    618 static int
    619 pre_TP(DECL_ARGS)
    620 {
    621 	const struct man_node	*nn;
    622 	size_t			 len;
    623 	int			 ival;
    624 
    625 	switch (n->type) {
    626 	case (MAN_HEAD):
    627 		p->flags |= TERMP_NOBREAK;
    628 		p->flags |= TERMP_TWOSPACE;
    629 		break;
    630 	case (MAN_BODY):
    631 		p->flags |= TERMP_NOLPAD;
    632 		p->flags |= TERMP_NOSPACE;
    633 		break;
    634 	case (MAN_BLOCK):
    635 		print_bvspace(p, n);
    636 		/* FALLTHROUGH */
    637 	default:
    638 		return(1);
    639 	}
    640 
    641 	len = (size_t)mt->lmargin;
    642 	ival = -1;
    643 
    644 	/* Calculate offset. */
    645 
    646 	if (NULL != (nn = n->parent->head->child))
    647 		if (NULL != nn->next)
    648 			if ((ival = arg2width(nn)) >= 0)
    649 				len = (size_t)ival;
    650 
    651 	switch (n->type) {
    652 	case (MAN_HEAD):
    653 		/* Handle zero-length properly. */
    654 		if (0 == len)
    655 			len = 1;
    656 
    657 		p->offset = mt->offset;
    658 		p->rmargin = mt->offset + len;
    659 
    660 		/* Don't print same-line elements. */
    661 		for (nn = n->child; nn; nn = nn->next)
    662 			if (nn->line > n->line)
    663 				print_node(p, mt, nn, m);
    664 
    665 		if (ival >= 0)
    666 			mt->lmargin = (size_t)ival;
    667 
    668 		return(0);
    669 	case (MAN_BODY):
    670 		p->offset = mt->offset + len;
    671 		p->rmargin = p->maxrmargin;
    672 		break;
    673 	default:
    674 		break;
    675 	}
    676 
    677 	return(1);
    678 }
    679 
    680 
    681 /* ARGSUSED */
    682 static void
    683 post_TP(DECL_ARGS)
    684 {
    685 
    686 	switch (n->type) {
    687 	case (MAN_HEAD):
    688 		term_flushln(p);
    689 		p->flags &= ~TERMP_NOBREAK;
    690 		p->flags &= ~TERMP_TWOSPACE;
    691 		p->rmargin = p->maxrmargin;
    692 		break;
    693 	case (MAN_BODY):
    694 		term_flushln(p);
    695 		p->flags &= ~TERMP_NOLPAD;
    696 		break;
    697 	default:
    698 		break;
    699 	}
    700 }
    701 
    702 
    703 /* ARGSUSED */
    704 static int
    705 pre_SS(DECL_ARGS)
    706 {
    707 
    708 	switch (n->type) {
    709 	case (MAN_BLOCK):
    710 		mt->lmargin = INDENT;
    711 		mt->offset = INDENT;
    712 		/* If following a prior empty `SS', no vspace. */
    713 		if (n->prev && MAN_SS == n->prev->tok)
    714 			if (NULL == n->prev->body->child)
    715 				break;
    716 		if (NULL == n->prev)
    717 			break;
    718 		term_vspace(p);
    719 		break;
    720 	case (MAN_HEAD):
    721 		p->bold++;
    722 		p->offset = HALFINDENT;
    723 		break;
    724 	case (MAN_BODY):
    725 		p->offset = mt->offset;
    726 		break;
    727 	default:
    728 		break;
    729 	}
    730 
    731 	return(1);
    732 }
    733 
    734 
    735 /* ARGSUSED */
    736 static void
    737 post_SS(DECL_ARGS)
    738 {
    739 
    740 	switch (n->type) {
    741 	case (MAN_HEAD):
    742 		term_newln(p);
    743 		p->bold--;
    744 		break;
    745 	case (MAN_BODY):
    746 		term_newln(p);
    747 		break;
    748 	default:
    749 		break;
    750 	}
    751 }
    752 
    753 
    754 /* ARGSUSED */
    755 static int
    756 pre_SH(DECL_ARGS)
    757 {
    758 
    759 	switch (n->type) {
    760 	case (MAN_BLOCK):
    761 		mt->lmargin = INDENT;
    762 		mt->offset = INDENT;
    763 		/* If following a prior empty `SH', no vspace. */
    764 		if (n->prev && MAN_SH == n->prev->tok)
    765 			if (NULL == n->prev->body->child)
    766 				break;
    767 		term_vspace(p);
    768 		break;
    769 	case (MAN_HEAD):
    770 		p->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 		p->bold--;
    793 		break;
    794 	case (MAN_BODY):
    795 		term_newln(p);
    796 		break;
    797 	default:
    798 		break;
    799 	}
    800 }
    801 
    802 
    803 /* ARGSUSED */
    804 static int
    805 pre_RS(DECL_ARGS)
    806 {
    807 	const struct man_node	*nn;
    808 	int			 ival;
    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 	if (NULL == (nn = n->parent->head->child)) {
    821 		mt->offset = mt->lmargin + INDENT;
    822 		p->offset = mt->offset;
    823 		return(1);
    824 	}
    825 
    826 	if ((ival = arg2width(nn)) < 0)
    827 		return(1);
    828 
    829 	mt->offset = INDENT + (size_t)ival;
    830 	p->offset = mt->offset;
    831 
    832 	return(1);
    833 }
    834 
    835 
    836 /* ARGSUSED */
    837 static void
    838 post_RS(DECL_ARGS)
    839 {
    840 
    841 	switch (n->type) {
    842 	case (MAN_BLOCK):
    843 		mt->offset = mt->lmargin = INDENT;
    844 		break;
    845 	default:
    846 		term_newln(p);
    847 		p->offset = INDENT;
    848 		break;
    849 	}
    850 }
    851 
    852 
    853 static void
    854 print_node(DECL_ARGS)
    855 {
    856 	int		 c, sz;
    857 
    858 	c = 1;
    859 
    860 	switch (n->type) {
    861 	case(MAN_TEXT):
    862 		if (0 == *n->string) {
    863 			term_vspace(p);
    864 			break;
    865 		}
    866 		/*
    867 		 * Note!  This is hacky.  Here, we recognise the `\c'
    868 		 * escape embedded in so many -man pages.  It's supposed
    869 		 * to remove the subsequent space, so we mark NOSPACE if
    870 		 * it's encountered in the string.
    871 		 */
    872 		sz = (int)strlen(n->string);
    873 		term_word(p, n->string);
    874 		if (sz >= 2 && n->string[sz - 1] == 'c' &&
    875 				n->string[sz - 2] == '\\')
    876 			p->flags |= TERMP_NOSPACE;
    877 		/* FIXME: this means that macro lines are munged!  */
    878 		if (MANT_LITERAL & mt->fl) {
    879 			p->flags |= TERMP_NOSPACE;
    880 			term_flushln(p);
    881 		}
    882 		break;
    883 	default:
    884 		if (termacts[n->tok].pre)
    885 			c = (*termacts[n->tok].pre)(p, mt, n, m);
    886 		break;
    887 	}
    888 
    889 	if (c && n->child)
    890 		print_body(p, mt, n->child, m);
    891 
    892 	if (MAN_TEXT != n->type)
    893 		if (termacts[n->tok].post)
    894 			(*termacts[n->tok].post)(p, mt, n, m);
    895 }
    896 
    897 
    898 static void
    899 print_body(DECL_ARGS)
    900 {
    901 
    902 	print_node(p, mt, n, m);
    903 	if ( ! n->next)
    904 		return;
    905 	print_body(p, mt, n->next, m);
    906 }
    907 
    908 
    909 static void
    910 print_foot(struct termp *p, const struct man_meta *meta)
    911 {
    912 	struct tm	*tm;
    913 	char		 buf[BUFSIZ];
    914 
    915 	tm = localtime(&meta->date);
    916 
    917 	if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
    918 		(void)strlcpy(buf, "(invalid date)", BUFSIZ);
    919 
    920 	term_vspace(p);
    921 
    922 	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
    923 	p->rmargin = p->maxrmargin - strlen(buf);
    924 	p->offset = 0;
    925 
    926 	if (meta->source)
    927 		term_word(p, meta->source);
    928 	if (meta->source)
    929 		term_word(p, "");
    930 	term_flushln(p);
    931 
    932 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
    933 	p->offset = p->rmargin;
    934 	p->rmargin = p->maxrmargin;
    935 	p->flags &= ~TERMP_NOBREAK;
    936 
    937 	term_word(p, buf);
    938 	term_flushln(p);
    939 }
    940 
    941 
    942 static void
    943 print_head(struct termp *p, const struct man_meta *meta)
    944 {
    945 	char		*buf, *title;
    946 
    947 	p->rmargin = p->maxrmargin;
    948 	p->offset = 0;
    949 
    950 	if (NULL == (buf = malloc(p->rmargin)))
    951 		err(EXIT_FAILURE, "malloc");
    952 	if (NULL == (title = malloc(p->rmargin)))
    953 		err(EXIT_FAILURE, "malloc");
    954 
    955 	if (meta->vol)
    956 		(void)strlcpy(buf, meta->vol, p->rmargin);
    957 	else
    958 		*buf = 0;
    959 
    960 	(void)snprintf(title, p->rmargin, "%s(%d)",
    961 			meta->title, meta->msec);
    962 
    963 	p->offset = 0;
    964 	p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2;
    965 	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
    966 
    967 	term_word(p, title);
    968 	term_flushln(p);
    969 
    970 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
    971 	p->offset = p->rmargin;
    972 	p->rmargin = p->maxrmargin - strlen(title);
    973 
    974 	term_word(p, buf);
    975 	term_flushln(p);
    976 
    977 	p->offset = p->rmargin;
    978 	p->rmargin = p->maxrmargin;
    979 	p->flags &= ~TERMP_NOBREAK;
    980 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
    981 
    982 	term_word(p, title);
    983 	term_flushln(p);
    984 
    985 	p->rmargin = p->maxrmargin;
    986 	p->offset = 0;
    987 	p->flags &= ~TERMP_NOSPACE;
    988 
    989 	free(title);
    990 	free(buf);
    991 }
    992 
    993