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