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