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