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