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