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