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