xref: /dflybsd-src/contrib/mdocml/man_term.c (revision 0eb2eccd5a86ef7dd7492d2651de55c3589f23d7)
1 /*	$Id: man_term.c,v 1.105 2011/03/22 10:13:01 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 		switch (p->enc) {
160 		case (TERMENC_ASCII):
161 			p->symtab = chars_init(CHARS_ASCII);
162 			break;
163 		default:
164 			abort();
165 			/* NOTREACHED */
166 		}
167 
168 	n = man_node(man);
169 	m = man_meta(man);
170 
171 	term_begin(p, print_man_head, print_man_foot, m);
172 	p->flags |= TERMP_NOSPACE;
173 
174 	mt.fl = 0;
175 	mt.lmargin = term_len(p, INDENT);
176 	mt.offset = term_len(p, INDENT);
177 
178 	if (n->child)
179 		print_man_nodelist(p, &mt, n->child, m);
180 
181 	term_end(p);
182 }
183 
184 
185 static size_t
186 a2height(const struct termp *p, const char *cp)
187 {
188 	struct roffsu	 su;
189 
190 	if ( ! a2roffsu(cp, &su, SCALE_VS))
191 		SCALE_VS_INIT(&su, term_strlen(p, cp));
192 
193 	return(term_vspan(p, &su));
194 }
195 
196 
197 static int
198 a2width(const struct termp *p, const char *cp)
199 {
200 	struct roffsu	 su;
201 
202 	if ( ! a2roffsu(cp, &su, SCALE_BU))
203 		return(-1);
204 
205 	return((int)term_hspan(p, &su));
206 }
207 
208 
209 static void
210 print_bvspace(struct termp *p, const struct man_node *n)
211 {
212 	term_newln(p);
213 
214 	if (n->body && n->body->child && MAN_TBL == n->body->child->type)
215 		return;
216 
217 	if (NULL == n->prev)
218 		return;
219 
220 	if (MAN_SS == n->prev->tok)
221 		return;
222 	if (MAN_SH == n->prev->tok)
223 		return;
224 
225 	term_vspace(p);
226 }
227 
228 
229 /* ARGSUSED */
230 static int
231 pre_ign(DECL_ARGS)
232 {
233 
234 	return(0);
235 }
236 
237 
238 /* ARGSUSED */
239 static int
240 pre_I(DECL_ARGS)
241 {
242 
243 	term_fontrepl(p, TERMFONT_UNDER);
244 	return(1);
245 }
246 
247 
248 /* ARGSUSED */
249 static int
250 pre_literal(DECL_ARGS)
251 {
252 
253 	term_newln(p);
254 
255 	if (MAN_nf == n->tok)
256 		mt->fl |= MANT_LITERAL;
257 	else
258 		mt->fl &= ~MANT_LITERAL;
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 	switch (n->tok) {
418 	case (MAN_br):
419 		len = 0;
420 		break;
421 	default:
422 		len = n->child ? a2height(p, n->child->string) : 1;
423 		break;
424 	}
425 
426 	if (0 == len)
427 		term_newln(p);
428 	for (i = 0; i < len; i++)
429 		term_vspace(p);
430 
431 	return(0);
432 }
433 
434 
435 /* ARGSUSED */
436 static int
437 pre_HP(DECL_ARGS)
438 {
439 	size_t			 len;
440 	int			 ival;
441 	const struct man_node	*nn;
442 
443 	switch (n->type) {
444 	case (MAN_BLOCK):
445 		print_bvspace(p, n);
446 		return(1);
447 	case (MAN_BODY):
448 		p->flags |= TERMP_NOBREAK;
449 		p->flags |= TERMP_TWOSPACE;
450 		break;
451 	default:
452 		return(0);
453 	}
454 
455 	len = mt->lmargin;
456 	ival = -1;
457 
458 	/* Calculate offset. */
459 
460 	if (NULL != (nn = n->parent->head->child))
461 		if ((ival = a2width(p, nn->string)) >= 0)
462 			len = (size_t)ival;
463 
464 	if (0 == len)
465 		len = term_len(p, 1);
466 
467 	p->offset = mt->offset;
468 	p->rmargin = mt->offset + len;
469 
470 	if (ival >= 0)
471 		mt->lmargin = (size_t)ival;
472 
473 	return(1);
474 }
475 
476 
477 /* ARGSUSED */
478 static void
479 post_HP(DECL_ARGS)
480 {
481 
482 	switch (n->type) {
483 	case (MAN_BLOCK):
484 		term_flushln(p);
485 		break;
486 	case (MAN_BODY):
487 		term_flushln(p);
488 		p->flags &= ~TERMP_NOBREAK;
489 		p->flags &= ~TERMP_TWOSPACE;
490 		p->offset = mt->offset;
491 		p->rmargin = p->maxrmargin;
492 		break;
493 	default:
494 		break;
495 	}
496 }
497 
498 
499 /* ARGSUSED */
500 static int
501 pre_PP(DECL_ARGS)
502 {
503 
504 	switch (n->type) {
505 	case (MAN_BLOCK):
506 		mt->lmargin = term_len(p, INDENT);
507 		print_bvspace(p, n);
508 		break;
509 	default:
510 		p->offset = mt->offset;
511 		break;
512 	}
513 
514 	return(MAN_HEAD != n->type);
515 }
516 
517 
518 /* ARGSUSED */
519 static int
520 pre_IP(DECL_ARGS)
521 {
522 	const struct man_node	*nn;
523 	size_t			 len;
524 	int			 savelit, ival;
525 
526 	switch (n->type) {
527 	case (MAN_BODY):
528 		p->flags |= TERMP_NOLPAD;
529 		p->flags |= TERMP_NOSPACE;
530 		break;
531 	case (MAN_HEAD):
532 		p->flags |= TERMP_NOBREAK;
533 		break;
534 	case (MAN_BLOCK):
535 		print_bvspace(p, n);
536 		/* FALLTHROUGH */
537 	default:
538 		return(1);
539 	}
540 
541 	len = mt->lmargin;
542 	ival = -1;
543 
544 	/* Calculate the offset from the optional second argument. */
545 	if (NULL != (nn = n->parent->head->child))
546 		if (NULL != (nn = nn->next))
547 			if ((ival = a2width(p, nn->string)) >= 0)
548 				len = (size_t)ival;
549 
550 	switch (n->type) {
551 	case (MAN_HEAD):
552 		/* Handle zero-width lengths. */
553 		if (0 == len)
554 			len = term_len(p, 1);
555 
556 		p->offset = mt->offset;
557 		p->rmargin = mt->offset + len;
558 		if (ival < 0)
559 			break;
560 
561 		/* Set the saved left-margin. */
562 		mt->lmargin = (size_t)ival;
563 
564 		savelit = MANT_LITERAL & mt->fl;
565 		mt->fl &= ~MANT_LITERAL;
566 
567 		if (n->child)
568 			print_man_node(p, mt, n->child, m);
569 
570 		if (savelit)
571 			mt->fl |= MANT_LITERAL;
572 
573 		return(0);
574 	case (MAN_BODY):
575 		p->offset = mt->offset + len;
576 		p->rmargin = p->maxrmargin;
577 		break;
578 	default:
579 		break;
580 	}
581 
582 	return(1);
583 }
584 
585 
586 /* ARGSUSED */
587 static void
588 post_IP(DECL_ARGS)
589 {
590 
591 	switch (n->type) {
592 	case (MAN_HEAD):
593 		term_flushln(p);
594 		p->flags &= ~TERMP_NOBREAK;
595 		p->rmargin = p->maxrmargin;
596 		break;
597 	case (MAN_BODY):
598 		term_newln(p);
599 		p->flags &= ~TERMP_NOLPAD;
600 		break;
601 	default:
602 		break;
603 	}
604 }
605 
606 
607 /* ARGSUSED */
608 static int
609 pre_TP(DECL_ARGS)
610 {
611 	const struct man_node	*nn;
612 	size_t			 len;
613 	int			 savelit, ival;
614 
615 	switch (n->type) {
616 	case (MAN_HEAD):
617 		p->flags |= TERMP_NOBREAK;
618 		break;
619 	case (MAN_BODY):
620 		p->flags |= TERMP_NOLPAD;
621 		p->flags |= TERMP_NOSPACE;
622 		break;
623 	case (MAN_BLOCK):
624 		print_bvspace(p, n);
625 		/* FALLTHROUGH */
626 	default:
627 		return(1);
628 	}
629 
630 	len = (size_t)mt->lmargin;
631 	ival = -1;
632 
633 	/* Calculate offset. */
634 
635 	if (NULL != (nn = n->parent->head->child)) {
636 		while (nn && MAN_TEXT != nn->type)
637 			nn = nn->next;
638 		if (nn && nn->next)
639 			if ((ival = a2width(p, nn->string)) >= 0)
640 				len = (size_t)ival;
641 	}
642 
643 	switch (n->type) {
644 	case (MAN_HEAD):
645 		/* Handle zero-length properly. */
646 		if (0 == len)
647 			len = term_len(p, 1);
648 
649 		p->offset = mt->offset;
650 		p->rmargin = mt->offset + len;
651 
652 		savelit = MANT_LITERAL & mt->fl;
653 		mt->fl &= ~MANT_LITERAL;
654 
655 		/* Don't print same-line elements. */
656 		for (nn = n->child; nn; nn = nn->next)
657 			if (nn->line > n->line)
658 				print_man_node(p, mt, nn, m);
659 
660 		if (savelit)
661 			mt->fl |= MANT_LITERAL;
662 
663 		if (ival >= 0)
664 			mt->lmargin = (size_t)ival;
665 
666 		return(0);
667 	case (MAN_BODY):
668 		p->offset = mt->offset + len;
669 		p->rmargin = p->maxrmargin;
670 		break;
671 	default:
672 		break;
673 	}
674 
675 	return(1);
676 }
677 
678 
679 /* ARGSUSED */
680 static void
681 post_TP(DECL_ARGS)
682 {
683 
684 	switch (n->type) {
685 	case (MAN_HEAD):
686 		term_flushln(p);
687 		p->flags &= ~TERMP_NOBREAK;
688 		p->flags &= ~TERMP_TWOSPACE;
689 		p->rmargin = p->maxrmargin;
690 		break;
691 	case (MAN_BODY):
692 		term_newln(p);
693 		p->flags &= ~TERMP_NOLPAD;
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->lmargin = term_len(p, INDENT);
709 		mt->offset = term_len(p, INDENT);
710 		/* If following a prior empty `SS', no vspace. */
711 		if (n->prev && MAN_SS == n->prev->tok)
712 			if (NULL == n->prev->body->child)
713 				break;
714 		if (NULL == n->prev)
715 			break;
716 		term_vspace(p);
717 		break;
718 	case (MAN_HEAD):
719 		term_fontrepl(p, TERMFONT_BOLD);
720 		p->offset = term_len(p, HALFINDENT);
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_SS(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_SH(DECL_ARGS)
754 {
755 
756 	switch (n->type) {
757 	case (MAN_BLOCK):
758 		mt->lmargin = term_len(p, INDENT);
759 		mt->offset = term_len(p, INDENT);
760 		/* If following a prior empty `SH', no vspace. */
761 		if (n->prev && MAN_SH == n->prev->tok)
762 			if (NULL == n->prev->body->child)
763 				break;
764 		/* If the first macro, no vspae. */
765 		if (NULL == n->prev)
766 			break;
767 		term_vspace(p);
768 		break;
769 	case (MAN_HEAD):
770 		term_fontrepl(p, TERMFONT_BOLD);
771 		p->offset = 0;
772 		break;
773 	case (MAN_BODY):
774 		p->offset = mt->offset;
775 		break;
776 	default:
777 		break;
778 	}
779 
780 	return(1);
781 }
782 
783 
784 /* ARGSUSED */
785 static void
786 post_SH(DECL_ARGS)
787 {
788 
789 	switch (n->type) {
790 	case (MAN_HEAD):
791 		term_newln(p);
792 		break;
793 	case (MAN_BODY):
794 		term_newln(p);
795 		break;
796 	default:
797 		break;
798 	}
799 }
800 
801 
802 /* ARGSUSED */
803 static int
804 pre_RS(DECL_ARGS)
805 {
806 	const struct man_node	*nn;
807 	int			 ival;
808 
809 	switch (n->type) {
810 	case (MAN_BLOCK):
811 		term_newln(p);
812 		return(1);
813 	case (MAN_HEAD):
814 		return(0);
815 	default:
816 		break;
817 	}
818 
819 	if (NULL == (nn = n->parent->head->child)) {
820 		mt->offset = mt->lmargin + term_len(p, INDENT);
821 		p->offset = mt->offset;
822 		return(1);
823 	}
824 
825 	if ((ival = a2width(p, nn->string)) < 0)
826 		return(1);
827 
828 	mt->offset = term_len(p, INDENT) + (size_t)ival;
829 	p->offset = mt->offset;
830 
831 	return(1);
832 }
833 
834 
835 /* ARGSUSED */
836 static void
837 post_RS(DECL_ARGS)
838 {
839 
840 	switch (n->type) {
841 	case (MAN_BLOCK):
842 		mt->offset = mt->lmargin = term_len(p, INDENT);
843 		break;
844 	case (MAN_HEAD):
845 		break;
846 	default:
847 		term_newln(p);
848 		p->offset = term_len(p, INDENT);
849 		break;
850 	}
851 }
852 
853 
854 static void
855 print_man_node(DECL_ARGS)
856 {
857 	size_t		 rm, rmax;
858 	int		 c;
859 
860 	switch (n->type) {
861 	case(MAN_TEXT):
862 		/*
863 		 * If we have a blank line, output a vertical space.
864 		 * If we have a space as the first character, break
865 		 * before printing the line's data.
866 		 */
867 		if ('\0' == *n->string) {
868 			term_vspace(p);
869 			return;
870 		} else if (' ' == *n->string && MAN_LINE & n->flags)
871 			term_newln(p);
872 
873 		term_word(p, n->string);
874 
875 		/*
876 		 * If we're in a literal context, make sure that words
877 		 * togehter on the same line stay together.  This is a
878 		 * POST-printing call, so we check the NEXT word.  Since
879 		 * -man doesn't have nested macros, we don't need to be
880 		 * more specific than this.
881 		 */
882 		if (MANT_LITERAL & mt->fl &&
883 				(NULL == n->next ||
884 				 n->next->line > n->line)) {
885 			rm = p->rmargin;
886 			rmax = p->maxrmargin;
887 			p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
888 			p->flags |= TERMP_NOSPACE;
889 			term_flushln(p);
890 			p->flags &= ~TERMP_NOLPAD;
891 			p->rmargin = rm;
892 			p->maxrmargin = rmax;
893 		}
894 
895 		if (MAN_EOS & n->flags)
896 			p->flags |= TERMP_SENTENCE;
897 		return;
898 	case (MAN_EQN):
899 		term_word(p, n->eqn->data);
900 		return;
901 	case (MAN_TBL):
902 		/*
903 		 * Tables are preceded by a newline.  Then process a
904 		 * table line, which will cause line termination,
905 		 */
906 		if (TBL_SPAN_FIRST & n->span->flags)
907 			term_newln(p);
908 		term_tbl(p, n->span);
909 		return;
910 	default:
911 		break;
912 	}
913 
914 	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
915 		term_fontrepl(p, TERMFONT_NONE);
916 
917 	c = 1;
918 	if (termacts[n->tok].pre)
919 		c = (*termacts[n->tok].pre)(p, mt, n, m);
920 
921 	if (c && n->child)
922 		print_man_nodelist(p, mt, n->child, m);
923 
924 	if (termacts[n->tok].post)
925 		(*termacts[n->tok].post)(p, mt, n, m);
926 	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
927 		term_fontrepl(p, TERMFONT_NONE);
928 
929 	if (MAN_EOS & n->flags)
930 		p->flags |= TERMP_SENTENCE;
931 }
932 
933 
934 static void
935 print_man_nodelist(DECL_ARGS)
936 {
937 
938 	print_man_node(p, mt, n, m);
939 	if ( ! n->next)
940 		return;
941 	print_man_nodelist(p, mt, n->next, m);
942 }
943 
944 
945 static void
946 print_man_foot(struct termp *p, const void *arg)
947 {
948 	const struct man_meta *meta;
949 
950 	meta = (const struct man_meta *)arg;
951 
952 	term_fontrepl(p, TERMFONT_NONE);
953 
954 	term_vspace(p);
955 	term_vspace(p);
956 	term_vspace(p);
957 
958 	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
959 	p->rmargin = p->maxrmargin - term_strlen(p, meta->date);
960 	p->offset = 0;
961 
962 	/* term_strlen() can return zero. */
963 	if (p->rmargin == p->maxrmargin)
964 		p->rmargin--;
965 
966 	if (meta->source)
967 		term_word(p, meta->source);
968 	if (meta->source)
969 		term_word(p, "");
970 	term_flushln(p);
971 
972 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
973 	p->offset = p->rmargin;
974 	p->rmargin = p->maxrmargin;
975 	p->flags &= ~TERMP_NOBREAK;
976 
977 	term_word(p, meta->date);
978 	term_flushln(p);
979 }
980 
981 
982 static void
983 print_man_head(struct termp *p, const void *arg)
984 {
985 	char		buf[BUFSIZ], title[BUFSIZ];
986 	size_t		buflen, titlen;
987 	const struct man_meta *m;
988 
989 	m = (const struct man_meta *)arg;
990 
991 	/*
992 	 * Note that old groff would spit out some spaces before the
993 	 * header.  We discontinue this strange behaviour, but at one
994 	 * point we did so here.
995 	 */
996 
997 	p->rmargin = p->maxrmargin;
998 
999 	p->offset = 0;
1000 	buf[0] = title[0] = '\0';
1001 
1002 	if (m->vol)
1003 		strlcpy(buf, m->vol, BUFSIZ);
1004 	buflen = term_strlen(p, buf);
1005 
1006 	snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec);
1007 	titlen = term_strlen(p, title);
1008 
1009 	p->offset = 0;
1010 	p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ?
1011 	    (p->maxrmargin -
1012 	     term_strlen(p, buf) + term_len(p, 1)) / 2 :
1013 	    p->maxrmargin - buflen;
1014 	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1015 
1016 	term_word(p, title);
1017 	term_flushln(p);
1018 
1019 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1020 	p->offset = p->rmargin;
1021 	p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
1022 	    p->maxrmargin - titlen : p->maxrmargin;
1023 
1024 	term_word(p, buf);
1025 	term_flushln(p);
1026 
1027 	p->flags &= ~TERMP_NOBREAK;
1028 	if (p->rmargin + titlen <= p->maxrmargin) {
1029 		p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1030 		p->offset = p->rmargin;
1031 		p->rmargin = p->maxrmargin;
1032 		term_word(p, title);
1033 		term_flushln(p);
1034 	}
1035 
1036 	p->rmargin = p->maxrmargin;
1037 	p->offset = 0;
1038 	p->flags &= ~TERMP_NOSPACE;
1039 
1040 	/*
1041 	 * Groff likes to have some leading spaces before content.  Well
1042 	 * that's fine by me.
1043 	 */
1044 
1045 	term_vspace(p);
1046 	term_vspace(p);
1047 	term_vspace(p);
1048 }
1049