Home | History | Annotate | Line # | Download | only in dist
man_term.c revision 1.8.2.2
      1 /*	Id: man_term.c,v 1.139 2013/12/22 23:34:13 schwarze Exp  */
      2 /*
      3  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps (at) bsd.lv>
      4  * Copyright (c) 2010, 2011, 2012, 2013 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	MAXMARGINS	  64 /* maximum number of indented scopes */
     37 
     38 struct	mtermp {
     39 	int		  fl;
     40 #define	MANT_LITERAL	 (1 << 0)
     41 	size_t		  lmargin[MAXMARGINS]; /* margins (incl. visible page) */
     42 	int		  lmargincur; /* index of current margin */
     43 	int		  lmarginsz; /* actual number of nested margins */
     44 	size_t		  offset; /* default offset to visible page */
     45 	int		  pardist; /* vert. space before par., unit: [v] */
     46 };
     47 
     48 #define	DECL_ARGS 	  struct termp *p, \
     49 			  struct mtermp *mt, \
     50 			  const struct man_node *n, \
     51 			  const struct man_meta *meta
     52 
     53 struct	termact {
     54 	int		(*pre)(DECL_ARGS);
     55 	void		(*post)(DECL_ARGS);
     56 	int		  flags;
     57 #define	MAN_NOTEXT	 (1 << 0) /* Never has text children. */
     58 };
     59 
     60 static	int		  a2width(const struct termp *, const char *);
     61 static	size_t		  a2height(const struct termp *, const char *);
     62 
     63 static	void		  print_man_nodelist(DECL_ARGS);
     64 static	void		  print_man_node(DECL_ARGS);
     65 static	void		  print_man_head(struct termp *, const void *);
     66 static	void		  print_man_foot(struct termp *, const void *);
     67 static	void		  print_bvspace(struct termp *,
     68 				const struct man_node *, int);
     69 
     70 static	int		  pre_B(DECL_ARGS);
     71 static	int		  pre_HP(DECL_ARGS);
     72 static	int		  pre_I(DECL_ARGS);
     73 static	int		  pre_IP(DECL_ARGS);
     74 static	int		  pre_OP(DECL_ARGS);
     75 static	int		  pre_PD(DECL_ARGS);
     76 static	int		  pre_PP(DECL_ARGS);
     77 static	int		  pre_RS(DECL_ARGS);
     78 static	int		  pre_SH(DECL_ARGS);
     79 static	int		  pre_SS(DECL_ARGS);
     80 static	int		  pre_TP(DECL_ARGS);
     81 static	int		  pre_UR(DECL_ARGS);
     82 static	int		  pre_alternate(DECL_ARGS);
     83 static	int		  pre_ft(DECL_ARGS);
     84 static	int		  pre_ign(DECL_ARGS);
     85 static	int		  pre_in(DECL_ARGS);
     86 static	int		  pre_literal(DECL_ARGS);
     87 static	int		  pre_sp(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 static	void		  post_UR(DECL_ARGS);
     96 
     97 static	const struct termact termacts[MAN_MAX] = {
     98 	{ pre_sp, NULL, MAN_NOTEXT }, /* br */
     99 	{ NULL, NULL, 0 }, /* TH */
    100 	{ pre_SH, post_SH, 0 }, /* SH */
    101 	{ pre_SS, post_SS, 0 }, /* SS */
    102 	{ pre_TP, post_TP, 0 }, /* TP */
    103 	{ pre_PP, NULL, 0 }, /* LP */
    104 	{ pre_PP, NULL, 0 }, /* PP */
    105 	{ pre_PP, NULL, 0 }, /* P */
    106 	{ pre_IP, post_IP, 0 }, /* IP */
    107 	{ pre_HP, post_HP, 0 }, /* HP */
    108 	{ NULL, NULL, 0 }, /* SM */
    109 	{ pre_B, NULL, 0 }, /* SB */
    110 	{ pre_alternate, NULL, 0 }, /* BI */
    111 	{ pre_alternate, NULL, 0 }, /* IB */
    112 	{ pre_alternate, NULL, 0 }, /* BR */
    113 	{ pre_alternate, NULL, 0 }, /* RB */
    114 	{ NULL, NULL, 0 }, /* R */
    115 	{ pre_B, NULL, 0 }, /* B */
    116 	{ pre_I, NULL, 0 }, /* I */
    117 	{ pre_alternate, NULL, 0 }, /* IR */
    118 	{ pre_alternate, NULL, 0 }, /* RI */
    119 	{ pre_ign, NULL, MAN_NOTEXT }, /* na */
    120 	{ pre_sp, NULL, MAN_NOTEXT }, /* sp */
    121 	{ pre_literal, NULL, 0 }, /* nf */
    122 	{ pre_literal, NULL, 0 }, /* fi */
    123 	{ NULL, NULL, 0 }, /* RE */
    124 	{ pre_RS, post_RS, 0 }, /* RS */
    125 	{ pre_ign, NULL, 0 }, /* DT */
    126 	{ pre_ign, NULL, 0 }, /* UC */
    127 	{ pre_PD, NULL, MAN_NOTEXT }, /* PD */
    128 	{ pre_ign, NULL, 0 }, /* AT */
    129 	{ pre_in, NULL, MAN_NOTEXT }, /* in */
    130 	{ pre_ft, NULL, MAN_NOTEXT }, /* ft */
    131 	{ pre_OP, NULL, 0 }, /* OP */
    132 	{ pre_literal, NULL, 0 }, /* EX */
    133 	{ pre_literal, NULL, 0 }, /* EE */
    134 	{ pre_UR, post_UR, 0 }, /* UR */
    135 	{ NULL, NULL, 0 }, /* UE */
    136 };
    137 
    138 
    139 
    140 void
    141 terminal_man(void *arg, const struct man *man)
    142 {
    143 	struct termp		*p;
    144 	const struct man_node	*n;
    145 	const struct man_meta	*meta;
    146 	struct mtermp		 mt;
    147 
    148 	p = (struct termp *)arg;
    149 
    150 	if (0 == p->defindent)
    151 		p->defindent = 7;
    152 
    153 	p->overstep = 0;
    154 	p->maxrmargin = p->defrmargin;
    155 	p->tabwidth = term_len(p, 5);
    156 
    157 	if (NULL == p->symtab)
    158 		p->symtab = mchars_alloc();
    159 
    160 	n = man_node(man);
    161 	meta = man_meta(man);
    162 
    163 	term_begin(p, print_man_head, print_man_foot, meta);
    164 	p->flags |= TERMP_NOSPACE;
    165 
    166 	memset(&mt, 0, sizeof(struct mtermp));
    167 
    168 	mt.lmargin[mt.lmargincur] = term_len(p, p->defindent);
    169 	mt.offset = term_len(p, p->defindent);
    170 	mt.pardist = 1;
    171 
    172 	if (n->child)
    173 		print_man_nodelist(p, &mt, n->child, meta);
    174 
    175 	term_end(p);
    176 }
    177 
    178 
    179 static size_t
    180 a2height(const struct termp *p, const char *cp)
    181 {
    182 	struct roffsu	 su;
    183 
    184 	if ( ! a2roffsu(cp, &su, SCALE_VS))
    185 		SCALE_VS_INIT(&su, atoi(cp));
    186 
    187 	return(term_vspan(p, &su));
    188 }
    189 
    190 
    191 static int
    192 a2width(const struct termp *p, const char *cp)
    193 {
    194 	struct roffsu	 su;
    195 
    196 	if ( ! a2roffsu(cp, &su, SCALE_BU))
    197 		return(-1);
    198 
    199 	return((int)term_hspan(p, &su));
    200 }
    201 
    202 /*
    203  * Printing leading vertical space before a block.
    204  * This is used for the paragraph macros.
    205  * The rules are pretty simple, since there's very little nesting going
    206  * on here.  Basically, if we're the first within another block (SS/SH),
    207  * then don't emit vertical space.  If we are (RS), then do.  If not the
    208  * first, print it.
    209  */
    210 static void
    211 print_bvspace(struct termp *p, const struct man_node *n, int pardist)
    212 {
    213 	int	 i;
    214 
    215 	term_newln(p);
    216 
    217 	if (n->body && n->body->child)
    218 		if (MAN_TBL == n->body->child->type)
    219 			return;
    220 
    221 	if (MAN_ROOT == n->parent->type || MAN_RS != n->parent->tok)
    222 		if (NULL == n->prev)
    223 			return;
    224 
    225 	for (i = 0; i < pardist; i++)
    226 		term_vspace(p);
    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 || MAN_EX == n->tok)
    256 		mt->fl |= MANT_LITERAL;
    257 	else
    258 		mt->fl &= ~MANT_LITERAL;
    259 
    260 	/*
    261 	 * Unlike .IP and .TP, .HP does not have a HEAD.
    262 	 * So in case a second call to term_flushln() is needed,
    263 	 * indentation has to be set up explicitly.
    264 	 */
    265 	if (MAN_HP == n->parent->tok && p->rmargin < p->maxrmargin) {
    266 		p->offset = p->rmargin;
    267 		p->rmargin = p->maxrmargin;
    268 		p->trailspace = 0;
    269 		p->flags &= ~TERMP_NOBREAK;
    270 		p->flags |= TERMP_NOSPACE;
    271 	}
    272 
    273 	return(0);
    274 }
    275 
    276 /* ARGSUSED */
    277 static int
    278 pre_PD(DECL_ARGS)
    279 {
    280 
    281 	n = n->child;
    282 	if (0 == n) {
    283 		mt->pardist = 1;
    284 		return(0);
    285 	}
    286 	assert(MAN_TEXT == n->type);
    287 	mt->pardist = atoi(n->string);
    288 	return(0);
    289 }
    290 
    291 /* ARGSUSED */
    292 static int
    293 pre_alternate(DECL_ARGS)
    294 {
    295 	enum termfont		 font[2];
    296 	const struct man_node	*nn;
    297 	int			 savelit, i;
    298 
    299 	switch (n->tok) {
    300 	case (MAN_RB):
    301 		font[0] = TERMFONT_NONE;
    302 		font[1] = TERMFONT_BOLD;
    303 		break;
    304 	case (MAN_RI):
    305 		font[0] = TERMFONT_NONE;
    306 		font[1] = TERMFONT_UNDER;
    307 		break;
    308 	case (MAN_BR):
    309 		font[0] = TERMFONT_BOLD;
    310 		font[1] = TERMFONT_NONE;
    311 		break;
    312 	case (MAN_BI):
    313 		font[0] = TERMFONT_BOLD;
    314 		font[1] = TERMFONT_UNDER;
    315 		break;
    316 	case (MAN_IR):
    317 		font[0] = TERMFONT_UNDER;
    318 		font[1] = TERMFONT_NONE;
    319 		break;
    320 	case (MAN_IB):
    321 		font[0] = TERMFONT_UNDER;
    322 		font[1] = TERMFONT_BOLD;
    323 		break;
    324 	default:
    325 		abort();
    326 	}
    327 
    328 	savelit = MANT_LITERAL & mt->fl;
    329 	mt->fl &= ~MANT_LITERAL;
    330 
    331 	for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) {
    332 		term_fontrepl(p, font[i]);
    333 		if (savelit && NULL == nn->next)
    334 			mt->fl |= MANT_LITERAL;
    335 		print_man_node(p, mt, nn, meta);
    336 		if (nn->next)
    337 			p->flags |= TERMP_NOSPACE;
    338 	}
    339 
    340 	return(0);
    341 }
    342 
    343 /* ARGSUSED */
    344 static int
    345 pre_B(DECL_ARGS)
    346 {
    347 
    348 	term_fontrepl(p, TERMFONT_BOLD);
    349 	return(1);
    350 }
    351 
    352 /* ARGSUSED */
    353 static int
    354 pre_OP(DECL_ARGS)
    355 {
    356 
    357 	term_word(p, "[");
    358 	p->flags |= TERMP_NOSPACE;
    359 
    360 	if (NULL != (n = n->child)) {
    361 		term_fontrepl(p, TERMFONT_BOLD);
    362 		term_word(p, n->string);
    363 	}
    364 	if (NULL != n && NULL != n->next) {
    365 		term_fontrepl(p, TERMFONT_UNDER);
    366 		term_word(p, n->next->string);
    367 	}
    368 
    369 	term_fontrepl(p, TERMFONT_NONE);
    370 	p->flags |= TERMP_NOSPACE;
    371 	term_word(p, "]");
    372 	return(0);
    373 }
    374 
    375 /* ARGSUSED */
    376 static int
    377 pre_ft(DECL_ARGS)
    378 {
    379 	const char	*cp;
    380 
    381 	if (NULL == n->child) {
    382 		term_fontlast(p);
    383 		return(0);
    384 	}
    385 
    386 	cp = n->child->string;
    387 	switch (*cp) {
    388 	case ('4'):
    389 		/* FALLTHROUGH */
    390 	case ('3'):
    391 		/* FALLTHROUGH */
    392 	case ('B'):
    393 		term_fontrepl(p, TERMFONT_BOLD);
    394 		break;
    395 	case ('2'):
    396 		/* FALLTHROUGH */
    397 	case ('I'):
    398 		term_fontrepl(p, TERMFONT_UNDER);
    399 		break;
    400 	case ('P'):
    401 		term_fontlast(p);
    402 		break;
    403 	case ('1'):
    404 		/* FALLTHROUGH */
    405 	case ('C'):
    406 		/* FALLTHROUGH */
    407 	case ('R'):
    408 		term_fontrepl(p, TERMFONT_NONE);
    409 		break;
    410 	default:
    411 		break;
    412 	}
    413 	return(0);
    414 }
    415 
    416 /* ARGSUSED */
    417 static int
    418 pre_in(DECL_ARGS)
    419 {
    420 	int		 len, less;
    421 	size_t		 v;
    422 	const char	*cp;
    423 
    424 	term_newln(p);
    425 
    426 	if (NULL == n->child) {
    427 		p->offset = mt->offset;
    428 		return(0);
    429 	}
    430 
    431 	cp = n->child->string;
    432 	less = 0;
    433 
    434 	if ('-' == *cp)
    435 		less = -1;
    436 	else if ('+' == *cp)
    437 		less = 1;
    438 	else
    439 		cp--;
    440 
    441 	if ((len = a2width(p, ++cp)) < 0)
    442 		return(0);
    443 
    444 	v = (size_t)len;
    445 
    446 	if (less < 0)
    447 		p->offset -= p->offset > v ? v : p->offset;
    448 	else if (less > 0)
    449 		p->offset += v;
    450 	else
    451 		p->offset = v;
    452 
    453 	/* Don't let this creep beyond the right margin. */
    454 
    455 	if (p->offset > p->rmargin)
    456 		p->offset = p->rmargin;
    457 
    458 	return(0);
    459 }
    460 
    461 
    462 /* ARGSUSED */
    463 static int
    464 pre_sp(DECL_ARGS)
    465 {
    466 	char		*s;
    467 	size_t		 i, len;
    468 	int		 neg;
    469 
    470 	if ((NULL == n->prev && n->parent)) {
    471 		switch (n->parent->tok) {
    472 		case (MAN_SH):
    473 			/* FALLTHROUGH */
    474 		case (MAN_SS):
    475 			/* FALLTHROUGH */
    476 		case (MAN_PP):
    477 			/* FALLTHROUGH */
    478 		case (MAN_LP):
    479 			/* FALLTHROUGH */
    480 		case (MAN_P):
    481 			/* FALLTHROUGH */
    482 			return(0);
    483 		default:
    484 			break;
    485 		}
    486 	}
    487 
    488 	neg = 0;
    489 	switch (n->tok) {
    490 	case (MAN_br):
    491 		len = 0;
    492 		break;
    493 	default:
    494 		if (NULL == n->child) {
    495 			len = 1;
    496 			break;
    497 		}
    498 		s = n->child->string;
    499 		if ('-' == *s) {
    500 			neg = 1;
    501 			s++;
    502 		}
    503 		len = a2height(p, s);
    504 		break;
    505 	}
    506 
    507 	if (0 == len)
    508 		term_newln(p);
    509 	else if (neg)
    510 		p->skipvsp += len;
    511 	else
    512 		for (i = 0; i < len; i++)
    513 			term_vspace(p);
    514 
    515 	return(0);
    516 }
    517 
    518 
    519 /* ARGSUSED */
    520 static int
    521 pre_HP(DECL_ARGS)
    522 {
    523 	size_t			 len, one;
    524 	int			 ival;
    525 	const struct man_node	*nn;
    526 
    527 	switch (n->type) {
    528 	case (MAN_BLOCK):
    529 		print_bvspace(p, n, mt->pardist);
    530 		return(1);
    531 	case (MAN_BODY):
    532 		break;
    533 	default:
    534 		return(0);
    535 	}
    536 
    537 	if ( ! (MANT_LITERAL & mt->fl)) {
    538 		p->flags |= TERMP_NOBREAK;
    539 		p->trailspace = 2;
    540 	}
    541 
    542 	len = mt->lmargin[mt->lmargincur];
    543 	ival = -1;
    544 
    545 	/* Calculate offset. */
    546 
    547 	if (NULL != (nn = n->parent->head->child))
    548 		if ((ival = a2width(p, nn->string)) >= 0)
    549 			len = (size_t)ival;
    550 
    551 	one = term_len(p, 1);
    552 	if (len < one)
    553 		len = one;
    554 
    555 	p->offset = mt->offset;
    556 	p->rmargin = mt->offset + len;
    557 
    558 	if (ival >= 0)
    559 		mt->lmargin[mt->lmargincur] = (size_t)ival;
    560 
    561 	return(1);
    562 }
    563 
    564 
    565 /* ARGSUSED */
    566 static void
    567 post_HP(DECL_ARGS)
    568 {
    569 
    570 	switch (n->type) {
    571 	case (MAN_BODY):
    572 		term_newln(p);
    573 		p->flags &= ~TERMP_NOBREAK;
    574 		p->trailspace = 0;
    575 		p->offset = mt->offset;
    576 		p->rmargin = p->maxrmargin;
    577 		break;
    578 	default:
    579 		break;
    580 	}
    581 }
    582 
    583 
    584 /* ARGSUSED */
    585 static int
    586 pre_PP(DECL_ARGS)
    587 {
    588 
    589 	switch (n->type) {
    590 	case (MAN_BLOCK):
    591 		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
    592 		print_bvspace(p, n, mt->pardist);
    593 		break;
    594 	default:
    595 		p->offset = mt->offset;
    596 		break;
    597 	}
    598 
    599 	return(MAN_HEAD != n->type);
    600 }
    601 
    602 
    603 /* ARGSUSED */
    604 static int
    605 pre_IP(DECL_ARGS)
    606 {
    607 	const struct man_node	*nn;
    608 	size_t			 len;
    609 	int			 savelit, ival;
    610 
    611 	switch (n->type) {
    612 	case (MAN_BODY):
    613 		p->flags |= TERMP_NOSPACE;
    614 		break;
    615 	case (MAN_HEAD):
    616 		p->flags |= TERMP_NOBREAK;
    617 		p->trailspace = 1;
    618 		break;
    619 	case (MAN_BLOCK):
    620 		print_bvspace(p, n, mt->pardist);
    621 		/* FALLTHROUGH */
    622 	default:
    623 		return(1);
    624 	}
    625 
    626 	len = mt->lmargin[mt->lmargincur];
    627 	ival = -1;
    628 
    629 	/* Calculate the offset from the optional second argument. */
    630 	if (NULL != (nn = n->parent->head->child))
    631 		if (NULL != (nn = nn->next))
    632 			if ((ival = a2width(p, nn->string)) >= 0)
    633 				len = (size_t)ival;
    634 
    635 	switch (n->type) {
    636 	case (MAN_HEAD):
    637 		/* Handle zero-width lengths. */
    638 		if (0 == len)
    639 			len = term_len(p, 1);
    640 
    641 		p->offset = mt->offset;
    642 		p->rmargin = mt->offset + len;
    643 		if (ival < 0)
    644 			break;
    645 
    646 		/* Set the saved left-margin. */
    647 		mt->lmargin[mt->lmargincur] = (size_t)ival;
    648 
    649 		savelit = MANT_LITERAL & mt->fl;
    650 		mt->fl &= ~MANT_LITERAL;
    651 
    652 		if (n->child)
    653 			print_man_node(p, mt, n->child, meta);
    654 
    655 		if (savelit)
    656 			mt->fl |= MANT_LITERAL;
    657 
    658 		return(0);
    659 	case (MAN_BODY):
    660 		p->offset = mt->offset + len;
    661 		p->rmargin = p->maxrmargin;
    662 		break;
    663 	default:
    664 		break;
    665 	}
    666 
    667 	return(1);
    668 }
    669 
    670 
    671 /* ARGSUSED */
    672 static void
    673 post_IP(DECL_ARGS)
    674 {
    675 
    676 	switch (n->type) {
    677 	case (MAN_HEAD):
    678 		term_flushln(p);
    679 		p->flags &= ~TERMP_NOBREAK;
    680 		p->trailspace = 0;
    681 		p->rmargin = p->maxrmargin;
    682 		break;
    683 	case (MAN_BODY):
    684 		term_newln(p);
    685 		p->offset = mt->offset;
    686 		break;
    687 	default:
    688 		break;
    689 	}
    690 }
    691 
    692 
    693 /* ARGSUSED */
    694 static int
    695 pre_TP(DECL_ARGS)
    696 {
    697 	const struct man_node	*nn;
    698 	size_t			 len;
    699 	int			 savelit, ival;
    700 
    701 	switch (n->type) {
    702 	case (MAN_HEAD):
    703 		p->flags |= TERMP_NOBREAK;
    704 		p->trailspace = 1;
    705 		break;
    706 	case (MAN_BODY):
    707 		p->flags |= TERMP_NOSPACE;
    708 		break;
    709 	case (MAN_BLOCK):
    710 		print_bvspace(p, n, mt->pardist);
    711 		/* FALLTHROUGH */
    712 	default:
    713 		return(1);
    714 	}
    715 
    716 	len = (size_t)mt->lmargin[mt->lmargincur];
    717 	ival = -1;
    718 
    719 	/* Calculate offset. */
    720 
    721 	if (NULL != (nn = n->parent->head->child))
    722 		if (nn->string && nn->parent->line == nn->line)
    723 			if ((ival = a2width(p, nn->string)) >= 0)
    724 				len = (size_t)ival;
    725 
    726 	switch (n->type) {
    727 	case (MAN_HEAD):
    728 		/* Handle zero-length properly. */
    729 		if (0 == len)
    730 			len = term_len(p, 1);
    731 
    732 		p->offset = mt->offset;
    733 		p->rmargin = mt->offset + len;
    734 
    735 		savelit = MANT_LITERAL & mt->fl;
    736 		mt->fl &= ~MANT_LITERAL;
    737 
    738 		/* Don't print same-line elements. */
    739 		for (nn = n->child; nn; nn = nn->next)
    740 			if (nn->line > n->line)
    741 				print_man_node(p, mt, nn, meta);
    742 
    743 		if (savelit)
    744 			mt->fl |= MANT_LITERAL;
    745 		if (ival >= 0)
    746 			mt->lmargin[mt->lmargincur] = (size_t)ival;
    747 
    748 		return(0);
    749 	case (MAN_BODY):
    750 		p->offset = mt->offset + len;
    751 		p->rmargin = p->maxrmargin;
    752 		p->trailspace = 0;
    753 		p->flags &= ~TERMP_NOBREAK;
    754 		break;
    755 	default:
    756 		break;
    757 	}
    758 
    759 	return(1);
    760 }
    761 
    762 
    763 /* ARGSUSED */
    764 static void
    765 post_TP(DECL_ARGS)
    766 {
    767 
    768 	switch (n->type) {
    769 	case (MAN_HEAD):
    770 		term_flushln(p);
    771 		break;
    772 	case (MAN_BODY):
    773 		term_newln(p);
    774 		p->offset = mt->offset;
    775 		break;
    776 	default:
    777 		break;
    778 	}
    779 }
    780 
    781 
    782 /* ARGSUSED */
    783 static int
    784 pre_SS(DECL_ARGS)
    785 {
    786 	int	 i;
    787 
    788 	switch (n->type) {
    789 	case (MAN_BLOCK):
    790 		mt->fl &= ~MANT_LITERAL;
    791 		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
    792 		mt->offset = term_len(p, p->defindent);
    793 		/* If following a prior empty `SS', no vspace. */
    794 		if (n->prev && MAN_SS == n->prev->tok)
    795 			if (NULL == n->prev->body->child)
    796 				break;
    797 		if (NULL == n->prev)
    798 			break;
    799 		for (i = 0; i < mt->pardist; i++)
    800 			term_vspace(p);
    801 		break;
    802 	case (MAN_HEAD):
    803 		term_fontrepl(p, TERMFONT_BOLD);
    804 		p->offset = term_len(p, 3);
    805 		break;
    806 	case (MAN_BODY):
    807 		p->offset = mt->offset;
    808 		break;
    809 	default:
    810 		break;
    811 	}
    812 
    813 	return(1);
    814 }
    815 
    816 
    817 /* ARGSUSED */
    818 static void
    819 post_SS(DECL_ARGS)
    820 {
    821 
    822 	switch (n->type) {
    823 	case (MAN_HEAD):
    824 		term_newln(p);
    825 		break;
    826 	case (MAN_BODY):
    827 		term_newln(p);
    828 		break;
    829 	default:
    830 		break;
    831 	}
    832 }
    833 
    834 
    835 /* ARGSUSED */
    836 static int
    837 pre_SH(DECL_ARGS)
    838 {
    839 	int	 i;
    840 
    841 	switch (n->type) {
    842 	case (MAN_BLOCK):
    843 		mt->fl &= ~MANT_LITERAL;
    844 		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
    845 		mt->offset = term_len(p, p->defindent);
    846 		/* If following a prior empty `SH', no vspace. */
    847 		if (n->prev && MAN_SH == n->prev->tok)
    848 			if (NULL == n->prev->body->child)
    849 				break;
    850 		/* If the first macro, no vspae. */
    851 		if (NULL == n->prev)
    852 			break;
    853 		for (i = 0; i < mt->pardist; i++)
    854 			term_vspace(p);
    855 		break;
    856 	case (MAN_HEAD):
    857 		term_fontrepl(p, TERMFONT_BOLD);
    858 		p->offset = 0;
    859 		break;
    860 	case (MAN_BODY):
    861 		p->offset = mt->offset;
    862 		break;
    863 	default:
    864 		break;
    865 	}
    866 
    867 	return(1);
    868 }
    869 
    870 
    871 /* ARGSUSED */
    872 static void
    873 post_SH(DECL_ARGS)
    874 {
    875 
    876 	switch (n->type) {
    877 	case (MAN_HEAD):
    878 		term_newln(p);
    879 		break;
    880 	case (MAN_BODY):
    881 		term_newln(p);
    882 		break;
    883 	default:
    884 		break;
    885 	}
    886 }
    887 
    888 /* ARGSUSED */
    889 static int
    890 pre_RS(DECL_ARGS)
    891 {
    892 	int		 ival;
    893 	size_t		 sz;
    894 
    895 	switch (n->type) {
    896 	case (MAN_BLOCK):
    897 		term_newln(p);
    898 		return(1);
    899 	case (MAN_HEAD):
    900 		return(0);
    901 	default:
    902 		break;
    903 	}
    904 
    905 	sz = term_len(p, p->defindent);
    906 
    907 	if (NULL != (n = n->parent->head->child))
    908 		if ((ival = a2width(p, n->string)) >= 0)
    909 			sz = (size_t)ival;
    910 
    911 	mt->offset += sz;
    912 	p->rmargin = p->maxrmargin;
    913 	p->offset = mt->offset < p->rmargin ? mt->offset : p->rmargin;
    914 
    915 	if (++mt->lmarginsz < MAXMARGINS)
    916 		mt->lmargincur = mt->lmarginsz;
    917 
    918 	mt->lmargin[mt->lmargincur] = mt->lmargin[mt->lmargincur - 1];
    919 	return(1);
    920 }
    921 
    922 /* ARGSUSED */
    923 static void
    924 post_RS(DECL_ARGS)
    925 {
    926 	int		 ival;
    927 	size_t		 sz;
    928 
    929 	switch (n->type) {
    930 	case (MAN_BLOCK):
    931 		return;
    932 	case (MAN_HEAD):
    933 		return;
    934 	default:
    935 		term_newln(p);
    936 		break;
    937 	}
    938 
    939 	sz = term_len(p, p->defindent);
    940 
    941 	if (NULL != (n = n->parent->head->child))
    942 		if ((ival = a2width(p, n->string)) >= 0)
    943 			sz = (size_t)ival;
    944 
    945 	mt->offset = mt->offset < sz ?  0 : mt->offset - sz;
    946 	p->offset = mt->offset;
    947 
    948 	if (--mt->lmarginsz < MAXMARGINS)
    949 		mt->lmargincur = mt->lmarginsz;
    950 }
    951 
    952 /* ARGSUSED */
    953 static int
    954 pre_UR(DECL_ARGS)
    955 {
    956 
    957 	return (MAN_HEAD != n->type);
    958 }
    959 
    960 /* ARGSUSED */
    961 static void
    962 post_UR(DECL_ARGS)
    963 {
    964 
    965 	if (MAN_BLOCK != n->type)
    966 		return;
    967 
    968 	term_word(p, "<");
    969 	p->flags |= TERMP_NOSPACE;
    970 
    971 	if (NULL != n->child->child)
    972 		print_man_node(p, mt, n->child->child, meta);
    973 
    974 	p->flags |= TERMP_NOSPACE;
    975 	term_word(p, ">");
    976 }
    977 
    978 static void
    979 print_man_node(DECL_ARGS)
    980 {
    981 	size_t		 rm, rmax;
    982 	int		 c;
    983 
    984 	switch (n->type) {
    985 	case(MAN_TEXT):
    986 		/*
    987 		 * If we have a blank line, output a vertical space.
    988 		 * If we have a space as the first character, break
    989 		 * before printing the line's data.
    990 		 */
    991 		if ('\0' == *n->string) {
    992 			term_vspace(p);
    993 			return;
    994 		} else if (' ' == *n->string && MAN_LINE & n->flags)
    995 			term_newln(p);
    996 
    997 		term_word(p, n->string);
    998 		goto out;
    999 
   1000 	case (MAN_EQN):
   1001 		term_eqn(p, n->eqn);
   1002 		return;
   1003 	case (MAN_TBL):
   1004 		/*
   1005 		 * Tables are preceded by a newline.  Then process a
   1006 		 * table line, which will cause line termination,
   1007 		 */
   1008 		if (TBL_SPAN_FIRST & n->span->flags)
   1009 			term_newln(p);
   1010 		term_tbl(p, n->span);
   1011 		return;
   1012 	default:
   1013 		break;
   1014 	}
   1015 
   1016 	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
   1017 		term_fontrepl(p, TERMFONT_NONE);
   1018 
   1019 	c = 1;
   1020 	if (termacts[n->tok].pre)
   1021 		c = (*termacts[n->tok].pre)(p, mt, n, meta);
   1022 
   1023 	if (c && n->child)
   1024 		print_man_nodelist(p, mt, n->child, meta);
   1025 
   1026 	if (termacts[n->tok].post)
   1027 		(*termacts[n->tok].post)(p, mt, n, meta);
   1028 	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
   1029 		term_fontrepl(p, TERMFONT_NONE);
   1030 
   1031 out:
   1032 	/*
   1033 	 * If we're in a literal context, make sure that words
   1034 	 * together on the same line stay together.  This is a
   1035 	 * POST-printing call, so we check the NEXT word.  Since
   1036 	 * -man doesn't have nested macros, we don't need to be
   1037 	 * more specific than this.
   1038 	 */
   1039 	if (MANT_LITERAL & mt->fl && ! (TERMP_NOBREAK & p->flags) &&
   1040 	    (NULL == n->next || n->next->line > n->line)) {
   1041 		rm = p->rmargin;
   1042 		rmax = p->maxrmargin;
   1043 		p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
   1044 		p->flags |= TERMP_NOSPACE;
   1045 		if (NULL != n->string && '\0' != *n->string)
   1046 			term_flushln(p);
   1047 		else
   1048 			term_newln(p);
   1049 		if (rm < rmax && n->parent->tok == MAN_HP) {
   1050 			p->offset = rm;
   1051 			p->rmargin = rmax;
   1052 		} else
   1053 			p->rmargin = rm;
   1054 		p->maxrmargin = rmax;
   1055 	}
   1056 	if (MAN_EOS & n->flags)
   1057 		p->flags |= TERMP_SENTENCE;
   1058 }
   1059 
   1060 
   1061 static void
   1062 print_man_nodelist(DECL_ARGS)
   1063 {
   1064 
   1065 	print_man_node(p, mt, n, meta);
   1066 	if ( ! n->next)
   1067 		return;
   1068 	print_man_nodelist(p, mt, n->next, meta);
   1069 }
   1070 
   1071 
   1072 static void
   1073 print_man_foot(struct termp *p, const void *arg)
   1074 {
   1075 	char		title[BUFSIZ];
   1076 	size_t		datelen;
   1077 	const struct man_meta *meta;
   1078 
   1079 	meta = (const struct man_meta *)arg;
   1080 	assert(meta->title);
   1081 	assert(meta->msec);
   1082 	assert(meta->date);
   1083 
   1084 	term_fontrepl(p, TERMFONT_NONE);
   1085 
   1086 	term_vspace(p);
   1087 
   1088 	/*
   1089 	 * Temporary, undocumented option to imitate mdoc(7) output.
   1090 	 * In the bottom right corner, use the source instead of
   1091 	 * the title.
   1092 	 */
   1093 
   1094 	if ( ! p->mdocstyle) {
   1095 		term_vspace(p);
   1096 		term_vspace(p);
   1097 		snprintf(title, BUFSIZ, "%s(%s)", meta->title, meta->msec);
   1098 	} else if (meta->source) {
   1099 		strlcpy(title, meta->source, BUFSIZ);
   1100 	} else {
   1101 		title[0] = '\0';
   1102 	}
   1103 	datelen = term_strlen(p, meta->date);
   1104 
   1105 	/* Bottom left corner: manual source. */
   1106 
   1107 	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
   1108 	p->trailspace = 1;
   1109 	p->offset = 0;
   1110 	p->rmargin = (p->maxrmargin - datelen + term_len(p, 1)) / 2;
   1111 
   1112 	if (meta->source)
   1113 		term_word(p, meta->source);
   1114 	term_flushln(p);
   1115 
   1116 	/* At the bottom in the middle: manual date. */
   1117 
   1118 	p->flags |= TERMP_NOSPACE;
   1119 	p->offset = p->rmargin;
   1120 	p->rmargin = p->maxrmargin - term_strlen(p, title);
   1121 	if (p->offset + datelen >= p->rmargin)
   1122 		p->rmargin = p->offset + datelen;
   1123 
   1124 	term_word(p, meta->date);
   1125 	term_flushln(p);
   1126 
   1127 	/* Bottom right corner: manual title and section. */
   1128 
   1129 	p->flags &= ~TERMP_NOBREAK;
   1130 	p->flags |= TERMP_NOSPACE;
   1131 	p->trailspace = 0;
   1132 	p->offset = p->rmargin;
   1133 	p->rmargin = p->maxrmargin;
   1134 
   1135 	term_word(p, title);
   1136 	term_flushln(p);
   1137 }
   1138 
   1139 
   1140 static void
   1141 print_man_head(struct termp *p, const void *arg)
   1142 {
   1143 	char		buf[BUFSIZ], title[BUFSIZ];
   1144 	size_t		buflen, titlen;
   1145 	const struct man_meta *meta;
   1146 
   1147 	meta = (const struct man_meta *)arg;
   1148 	assert(meta->title);
   1149 	assert(meta->msec);
   1150 
   1151 	if (meta->vol)
   1152 		strlcpy(buf, meta->vol, BUFSIZ);
   1153 	else
   1154 		buf[0] = '\0';
   1155 	buflen = term_strlen(p, buf);
   1156 
   1157 	/* Top left corner: manual title and section. */
   1158 
   1159 	snprintf(title, BUFSIZ, "%s(%s)", meta->title, meta->msec);
   1160 	titlen = term_strlen(p, title);
   1161 
   1162 	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
   1163 	p->trailspace = 1;
   1164 	p->offset = 0;
   1165 	p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ?
   1166 	    (p->maxrmargin -
   1167 	     term_strlen(p, buf) + term_len(p, 1)) / 2 :
   1168 	    p->maxrmargin - buflen;
   1169 
   1170 	term_word(p, title);
   1171 	term_flushln(p);
   1172 
   1173 	/* At the top in the middle: manual volume. */
   1174 
   1175 	p->flags |= TERMP_NOSPACE;
   1176 	p->offset = p->rmargin;
   1177 	p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
   1178 	    p->maxrmargin - titlen : p->maxrmargin;
   1179 
   1180 	term_word(p, buf);
   1181 	term_flushln(p);
   1182 
   1183 	/* Top right corner: title and section, again. */
   1184 
   1185 	p->flags &= ~TERMP_NOBREAK;
   1186 	p->trailspace = 0;
   1187 	if (p->rmargin + titlen <= p->maxrmargin) {
   1188 		p->flags |= TERMP_NOSPACE;
   1189 		p->offset = p->rmargin;
   1190 		p->rmargin = p->maxrmargin;
   1191 		term_word(p, title);
   1192 		term_flushln(p);
   1193 	}
   1194 
   1195 	p->flags &= ~TERMP_NOSPACE;
   1196 	p->offset = 0;
   1197 	p->rmargin = p->maxrmargin;
   1198 
   1199 	/*
   1200 	 * Groff prints three blank lines before the content.
   1201 	 * Do the same, except in the temporary, undocumented
   1202 	 * mode imitating mdoc(7) output.
   1203 	 */
   1204 
   1205 	term_vspace(p);
   1206 	if ( ! p->mdocstyle) {
   1207 		term_vspace(p);
   1208 		term_vspace(p);
   1209 	}
   1210 }
   1211