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