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