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