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