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