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