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