xref: /illumos-gate/usr/src/cmd/mandoc/man_term.c (revision bde334a8dbd66dfa70ce4d7fc9dcad6e1ae45fe4)
1 /*	$Id: man_term.c,v 1.211 2018/06/10 15:12:35 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2010-2015, 2017, 2018 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 AUTHORS DISCLAIM ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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 "config.h"
19 
20 #include <sys/types.h>
21 
22 #include <assert.h>
23 #include <ctype.h>
24 #include <limits.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include "mandoc_aux.h"
30 #include "mandoc.h"
31 #include "roff.h"
32 #include "man.h"
33 #include "out.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 	int		  lmargin[MAXMARGINS]; /* margins (incl. vis. 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 			  struct roff_node *n, \
52 			  const struct roff_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	void		  print_man_nodelist(DECL_ARGS);
62 static	void		  print_man_node(DECL_ARGS);
63 static	void		  print_man_head(struct termp *,
64 				const struct roff_meta *);
65 static	void		  print_man_foot(struct termp *,
66 				const struct roff_meta *);
67 static	void		  print_bvspace(struct termp *,
68 				const struct roff_node *, int);
69 
70 static	int		  pre_B(DECL_ARGS);
71 static	int		  pre_DT(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_ign(DECL_ARGS);
85 static	int		  pre_in(DECL_ARGS);
86 static	int		  pre_literal(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 static	void		  post_UR(DECL_ARGS);
95 
96 static	const struct termact __termacts[MAN_MAX - MAN_TH] = {
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_literal, NULL, 0 }, /* nf */
118 	{ pre_literal, NULL, 0 }, /* fi */
119 	{ NULL, NULL, 0 }, /* RE */
120 	{ pre_RS, post_RS, 0 }, /* RS */
121 	{ pre_DT, NULL, 0 }, /* DT */
122 	{ pre_ign, NULL, MAN_NOTEXT }, /* UC */
123 	{ pre_PD, NULL, MAN_NOTEXT }, /* PD */
124 	{ pre_ign, NULL, 0 }, /* AT */
125 	{ pre_in, NULL, MAN_NOTEXT }, /* in */
126 	{ pre_OP, NULL, 0 }, /* OP */
127 	{ pre_literal, NULL, 0 }, /* EX */
128 	{ pre_literal, NULL, 0 }, /* EE */
129 	{ pre_UR, post_UR, 0 }, /* UR */
130 	{ NULL, NULL, 0 }, /* UE */
131 	{ pre_UR, post_UR, 0 }, /* MT */
132 	{ NULL, NULL, 0 }, /* ME */
133 };
134 static	const struct termact *termacts = __termacts - MAN_TH;
135 
136 
137 void
138 terminal_man(void *arg, const struct roff_man *man)
139 {
140 	struct termp		*p;
141 	struct roff_node	*n;
142 	struct mtermp		 mt;
143 	size_t			 save_defindent;
144 
145 	p = (struct termp *)arg;
146 	save_defindent = p->defindent;
147 	if (p->synopsisonly == 0 && p->defindent == 0)
148 		p->defindent = 7;
149 	p->tcol->rmargin = p->maxrmargin = p->defrmargin;
150 	term_tab_set(p, NULL);
151 	term_tab_set(p, "T");
152 	term_tab_set(p, ".5i");
153 
154 	memset(&mt, 0, sizeof(struct mtermp));
155 	mt.lmargin[mt.lmargincur] = term_len(p, p->defindent);
156 	mt.offset = term_len(p, p->defindent);
157 	mt.pardist = 1;
158 
159 	n = man->first->child;
160 	if (p->synopsisonly) {
161 		while (n != NULL) {
162 			if (n->tok == MAN_SH &&
163 			    n->child->child->type == ROFFT_TEXT &&
164 			    !strcmp(n->child->child->string, "SYNOPSIS")) {
165 				if (n->child->next->child != NULL)
166 					print_man_nodelist(p, &mt,
167 					    n->child->next->child,
168 					    &man->meta);
169 				term_newln(p);
170 				break;
171 			}
172 			n = n->next;
173 		}
174 	} else {
175 		term_begin(p, print_man_head, print_man_foot, &man->meta);
176 		p->flags |= TERMP_NOSPACE;
177 		if (n != NULL)
178 			print_man_nodelist(p, &mt, n, &man->meta);
179 		term_end(p);
180 	}
181 	p->defindent = save_defindent;
182 }
183 
184 /*
185  * Printing leading vertical space before a block.
186  * This is used for the paragraph macros.
187  * The rules are pretty simple, since there's very little nesting going
188  * on here.  Basically, if we're the first within another block (SS/SH),
189  * then don't emit vertical space.  If we are (RS), then do.  If not the
190  * first, print it.
191  */
192 static void
193 print_bvspace(struct termp *p, const struct roff_node *n, int pardist)
194 {
195 	int	 i;
196 
197 	term_newln(p);
198 
199 	if (n->body && n->body->child)
200 		if (n->body->child->type == ROFFT_TBL)
201 			return;
202 
203 	if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS)
204 		if (NULL == n->prev)
205 			return;
206 
207 	for (i = 0; i < pardist; i++)
208 		term_vspace(p);
209 }
210 
211 
212 static int
213 pre_ign(DECL_ARGS)
214 {
215 
216 	return 0;
217 }
218 
219 static int
220 pre_I(DECL_ARGS)
221 {
222 
223 	term_fontrepl(p, TERMFONT_UNDER);
224 	return 1;
225 }
226 
227 static int
228 pre_literal(DECL_ARGS)
229 {
230 
231 	term_newln(p);
232 
233 	if (n->tok == MAN_nf || n->tok == MAN_EX)
234 		mt->fl |= MANT_LITERAL;
235 	else
236 		mt->fl &= ~MANT_LITERAL;
237 
238 	/*
239 	 * Unlike .IP and .TP, .HP does not have a HEAD.
240 	 * So in case a second call to term_flushln() is needed,
241 	 * indentation has to be set up explicitly.
242 	 */
243 	if (n->parent->tok == MAN_HP && p->tcol->rmargin < p->maxrmargin) {
244 		p->tcol->offset = p->tcol->rmargin;
245 		p->tcol->rmargin = p->maxrmargin;
246 		p->trailspace = 0;
247 		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
248 		p->flags |= TERMP_NOSPACE;
249 	}
250 
251 	return 0;
252 }
253 
254 static int
255 pre_PD(DECL_ARGS)
256 {
257 	struct roffsu	 su;
258 
259 	n = n->child;
260 	if (n == NULL) {
261 		mt->pardist = 1;
262 		return 0;
263 	}
264 	assert(n->type == ROFFT_TEXT);
265 	if (a2roffsu(n->string, &su, SCALE_VS) != NULL)
266 		mt->pardist = term_vspan(p, &su);
267 	return 0;
268 }
269 
270 static int
271 pre_alternate(DECL_ARGS)
272 {
273 	enum termfont		 font[2];
274 	struct roff_node	*nn;
275 	int			 savelit, i;
276 
277 	switch (n->tok) {
278 	case MAN_RB:
279 		font[0] = TERMFONT_NONE;
280 		font[1] = TERMFONT_BOLD;
281 		break;
282 	case MAN_RI:
283 		font[0] = TERMFONT_NONE;
284 		font[1] = TERMFONT_UNDER;
285 		break;
286 	case MAN_BR:
287 		font[0] = TERMFONT_BOLD;
288 		font[1] = TERMFONT_NONE;
289 		break;
290 	case MAN_BI:
291 		font[0] = TERMFONT_BOLD;
292 		font[1] = TERMFONT_UNDER;
293 		break;
294 	case MAN_IR:
295 		font[0] = TERMFONT_UNDER;
296 		font[1] = TERMFONT_NONE;
297 		break;
298 	case MAN_IB:
299 		font[0] = TERMFONT_UNDER;
300 		font[1] = TERMFONT_BOLD;
301 		break;
302 	default:
303 		abort();
304 	}
305 
306 	savelit = MANT_LITERAL & mt->fl;
307 	mt->fl &= ~MANT_LITERAL;
308 
309 	for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) {
310 		term_fontrepl(p, font[i]);
311 		if (savelit && NULL == nn->next)
312 			mt->fl |= MANT_LITERAL;
313 		assert(nn->type == ROFFT_TEXT);
314 		term_word(p, nn->string);
315 		if (nn->flags & NODE_EOS)
316                 	p->flags |= TERMP_SENTENCE;
317 		if (nn->next)
318 			p->flags |= TERMP_NOSPACE;
319 	}
320 
321 	return 0;
322 }
323 
324 static int
325 pre_B(DECL_ARGS)
326 {
327 
328 	term_fontrepl(p, TERMFONT_BOLD);
329 	return 1;
330 }
331 
332 static int
333 pre_OP(DECL_ARGS)
334 {
335 
336 	term_word(p, "[");
337 	p->flags |= TERMP_NOSPACE;
338 
339 	if (NULL != (n = n->child)) {
340 		term_fontrepl(p, TERMFONT_BOLD);
341 		term_word(p, n->string);
342 	}
343 	if (NULL != n && NULL != n->next) {
344 		term_fontrepl(p, TERMFONT_UNDER);
345 		term_word(p, n->next->string);
346 	}
347 
348 	term_fontrepl(p, TERMFONT_NONE);
349 	p->flags |= TERMP_NOSPACE;
350 	term_word(p, "]");
351 	return 0;
352 }
353 
354 static int
355 pre_in(DECL_ARGS)
356 {
357 	struct roffsu	 su;
358 	const char	*cp;
359 	size_t		 v;
360 	int		 less;
361 
362 	term_newln(p);
363 
364 	if (n->child == NULL) {
365 		p->tcol->offset = mt->offset;
366 		return 0;
367 	}
368 
369 	cp = n->child->string;
370 	less = 0;
371 
372 	if ('-' == *cp)
373 		less = -1;
374 	else if ('+' == *cp)
375 		less = 1;
376 	else
377 		cp--;
378 
379 	if (a2roffsu(++cp, &su, SCALE_EN) == NULL)
380 		return 0;
381 
382 	v = term_hen(p, &su);
383 
384 	if (less < 0)
385 		p->tcol->offset -= p->tcol->offset > v ? v : p->tcol->offset;
386 	else if (less > 0)
387 		p->tcol->offset += v;
388 	else
389 		p->tcol->offset = v;
390 	if (p->tcol->offset > SHRT_MAX)
391 		p->tcol->offset = term_len(p, p->defindent);
392 
393 	return 0;
394 }
395 
396 static int
397 pre_DT(DECL_ARGS)
398 {
399 	term_tab_set(p, NULL);
400 	term_tab_set(p, "T");
401 	term_tab_set(p, ".5i");
402 	return 0;
403 }
404 
405 static int
406 pre_HP(DECL_ARGS)
407 {
408 	struct roffsu		 su;
409 	const struct roff_node	*nn;
410 	int			 len;
411 
412 	switch (n->type) {
413 	case ROFFT_BLOCK:
414 		print_bvspace(p, n, mt->pardist);
415 		return 1;
416 	case ROFFT_BODY:
417 		break;
418 	default:
419 		return 0;
420 	}
421 
422 	if ( ! (MANT_LITERAL & mt->fl)) {
423 		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
424 		p->trailspace = 2;
425 	}
426 
427 	/* Calculate offset. */
428 
429 	if ((nn = n->parent->head->child) != NULL &&
430 	    a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
431 		len = term_hen(p, &su);
432 		if (len < 0 && (size_t)(-len) > mt->offset)
433 			len = -mt->offset;
434 		else if (len > SHRT_MAX)
435 			len = term_len(p, p->defindent);
436 		mt->lmargin[mt->lmargincur] = len;
437 	} else
438 		len = mt->lmargin[mt->lmargincur];
439 
440 	p->tcol->offset = mt->offset;
441 	p->tcol->rmargin = mt->offset + len;
442 	return 1;
443 }
444 
445 static void
446 post_HP(DECL_ARGS)
447 {
448 
449 	switch (n->type) {
450 	case ROFFT_BODY:
451 		term_newln(p);
452 
453 		/*
454 		 * Compatibility with a groff bug.
455 		 * The .HP macro uses the undocumented .tag request
456 		 * which causes a line break and cancels no-space
457 		 * mode even if there isn't any output.
458 		 */
459 
460 		if (n->child == NULL)
461 			term_vspace(p);
462 
463 		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
464 		p->trailspace = 0;
465 		p->tcol->offset = mt->offset;
466 		p->tcol->rmargin = p->maxrmargin;
467 		break;
468 	default:
469 		break;
470 	}
471 }
472 
473 static int
474 pre_PP(DECL_ARGS)
475 {
476 
477 	switch (n->type) {
478 	case ROFFT_BLOCK:
479 		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
480 		print_bvspace(p, n, mt->pardist);
481 		break;
482 	default:
483 		p->tcol->offset = mt->offset;
484 		break;
485 	}
486 
487 	return n->type != ROFFT_HEAD;
488 }
489 
490 static int
491 pre_IP(DECL_ARGS)
492 {
493 	struct roffsu		 su;
494 	const struct roff_node	*nn;
495 	int			 len, savelit;
496 
497 	switch (n->type) {
498 	case ROFFT_BODY:
499 		p->flags |= TERMP_NOSPACE;
500 		break;
501 	case ROFFT_HEAD:
502 		p->flags |= TERMP_NOBREAK;
503 		p->trailspace = 1;
504 		break;
505 	case ROFFT_BLOCK:
506 		print_bvspace(p, n, mt->pardist);
507 		/* FALLTHROUGH */
508 	default:
509 		return 1;
510 	}
511 
512 	/* Calculate the offset from the optional second argument. */
513 	if ((nn = n->parent->head->child) != NULL &&
514 	    (nn = nn->next) != NULL &&
515 	    a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
516 		len = term_hen(p, &su);
517 		if (len < 0 && (size_t)(-len) > mt->offset)
518 			len = -mt->offset;
519 		else if (len > SHRT_MAX)
520 			len = term_len(p, p->defindent);
521 		mt->lmargin[mt->lmargincur] = len;
522 	} else
523 		len = mt->lmargin[mt->lmargincur];
524 
525 	switch (n->type) {
526 	case ROFFT_HEAD:
527 		p->tcol->offset = mt->offset;
528 		p->tcol->rmargin = mt->offset + len;
529 
530 		savelit = MANT_LITERAL & mt->fl;
531 		mt->fl &= ~MANT_LITERAL;
532 
533 		if (n->child)
534 			print_man_node(p, mt, n->child, meta);
535 
536 		if (savelit)
537 			mt->fl |= MANT_LITERAL;
538 
539 		return 0;
540 	case ROFFT_BODY:
541 		p->tcol->offset = mt->offset + len;
542 		p->tcol->rmargin = p->maxrmargin;
543 		break;
544 	default:
545 		break;
546 	}
547 
548 	return 1;
549 }
550 
551 static void
552 post_IP(DECL_ARGS)
553 {
554 
555 	switch (n->type) {
556 	case ROFFT_HEAD:
557 		term_flushln(p);
558 		p->flags &= ~TERMP_NOBREAK;
559 		p->trailspace = 0;
560 		p->tcol->rmargin = p->maxrmargin;
561 		break;
562 	case ROFFT_BODY:
563 		term_newln(p);
564 		p->tcol->offset = mt->offset;
565 		break;
566 	default:
567 		break;
568 	}
569 }
570 
571 static int
572 pre_TP(DECL_ARGS)
573 {
574 	struct roffsu		 su;
575 	struct roff_node	*nn;
576 	int			 len, savelit;
577 
578 	switch (n->type) {
579 	case ROFFT_HEAD:
580 		p->flags |= TERMP_NOBREAK | TERMP_BRTRSP;
581 		p->trailspace = 1;
582 		break;
583 	case ROFFT_BODY:
584 		p->flags |= TERMP_NOSPACE;
585 		break;
586 	case ROFFT_BLOCK:
587 		print_bvspace(p, n, mt->pardist);
588 		/* FALLTHROUGH */
589 	default:
590 		return 1;
591 	}
592 
593 	/* Calculate offset. */
594 
595 	if ((nn = n->parent->head->child) != NULL &&
596 	    nn->string != NULL && ! (NODE_LINE & nn->flags) &&
597 	    a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
598 		len = term_hen(p, &su);
599 		if (len < 0 && (size_t)(-len) > mt->offset)
600 			len = -mt->offset;
601 		else if (len > SHRT_MAX)
602 			len = term_len(p, p->defindent);
603 		mt->lmargin[mt->lmargincur] = len;
604 	} else
605 		len = mt->lmargin[mt->lmargincur];
606 
607 	switch (n->type) {
608 	case ROFFT_HEAD:
609 		p->tcol->offset = mt->offset;
610 		p->tcol->rmargin = mt->offset + len;
611 
612 		savelit = MANT_LITERAL & mt->fl;
613 		mt->fl &= ~MANT_LITERAL;
614 
615 		/* Don't print same-line elements. */
616 		nn = n->child;
617 		while (NULL != nn && 0 == (NODE_LINE & nn->flags))
618 			nn = nn->next;
619 
620 		while (NULL != nn) {
621 			print_man_node(p, mt, nn, meta);
622 			nn = nn->next;
623 		}
624 
625 		if (savelit)
626 			mt->fl |= MANT_LITERAL;
627 		return 0;
628 	case ROFFT_BODY:
629 		p->tcol->offset = mt->offset + len;
630 		p->tcol->rmargin = p->maxrmargin;
631 		p->trailspace = 0;
632 		p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP);
633 		break;
634 	default:
635 		break;
636 	}
637 
638 	return 1;
639 }
640 
641 static void
642 post_TP(DECL_ARGS)
643 {
644 
645 	switch (n->type) {
646 	case ROFFT_HEAD:
647 		term_flushln(p);
648 		break;
649 	case ROFFT_BODY:
650 		term_newln(p);
651 		p->tcol->offset = mt->offset;
652 		break;
653 	default:
654 		break;
655 	}
656 }
657 
658 static int
659 pre_SS(DECL_ARGS)
660 {
661 	int	 i;
662 
663 	switch (n->type) {
664 	case ROFFT_BLOCK:
665 		mt->fl &= ~MANT_LITERAL;
666 		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
667 		mt->offset = term_len(p, p->defindent);
668 
669 		/*
670 		 * No vertical space before the first subsection
671 		 * and after an empty subsection.
672 		 */
673 
674 		do {
675 			n = n->prev;
676 		} while (n != NULL && n->tok >= MAN_TH &&
677 		    termacts[n->tok].flags & MAN_NOTEXT);
678 		if (n == NULL || n->type == ROFFT_COMMENT ||
679 		    (n->tok == MAN_SS && n->body->child == NULL))
680 			break;
681 
682 		for (i = 0; i < mt->pardist; i++)
683 			term_vspace(p);
684 		break;
685 	case ROFFT_HEAD:
686 		term_fontrepl(p, TERMFONT_BOLD);
687 		p->tcol->offset = term_len(p, 3);
688 		p->tcol->rmargin = mt->offset;
689 		p->trailspace = mt->offset;
690 		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
691 		break;
692 	case ROFFT_BODY:
693 		p->tcol->offset = mt->offset;
694 		p->tcol->rmargin = p->maxrmargin;
695 		p->trailspace = 0;
696 		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
697 		break;
698 	default:
699 		break;
700 	}
701 
702 	return 1;
703 }
704 
705 static void
706 post_SS(DECL_ARGS)
707 {
708 
709 	switch (n->type) {
710 	case ROFFT_HEAD:
711 		term_newln(p);
712 		break;
713 	case ROFFT_BODY:
714 		term_newln(p);
715 		break;
716 	default:
717 		break;
718 	}
719 }
720 
721 static int
722 pre_SH(DECL_ARGS)
723 {
724 	int	 i;
725 
726 	switch (n->type) {
727 	case ROFFT_BLOCK:
728 		mt->fl &= ~MANT_LITERAL;
729 		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
730 		mt->offset = term_len(p, p->defindent);
731 
732 		/*
733 		 * No vertical space before the first section
734 		 * and after an empty section.
735 		 */
736 
737 		do {
738 			n = n->prev;
739 		} while (n != NULL && n->tok >= MAN_TH &&
740 		    termacts[n->tok].flags & MAN_NOTEXT);
741 		if (n == NULL || n->type == ROFFT_COMMENT ||
742 		    (n->tok == MAN_SH && n->body->child == NULL))
743 			break;
744 
745 		for (i = 0; i < mt->pardist; i++)
746 			term_vspace(p);
747 		break;
748 	case ROFFT_HEAD:
749 		term_fontrepl(p, TERMFONT_BOLD);
750 		p->tcol->offset = 0;
751 		p->tcol->rmargin = mt->offset;
752 		p->trailspace = mt->offset;
753 		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
754 		break;
755 	case ROFFT_BODY:
756 		p->tcol->offset = mt->offset;
757 		p->tcol->rmargin = p->maxrmargin;
758 		p->trailspace = 0;
759 		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
760 		break;
761 	default:
762 		break;
763 	}
764 
765 	return 1;
766 }
767 
768 static void
769 post_SH(DECL_ARGS)
770 {
771 
772 	switch (n->type) {
773 	case ROFFT_HEAD:
774 		term_newln(p);
775 		break;
776 	case ROFFT_BODY:
777 		term_newln(p);
778 		break;
779 	default:
780 		break;
781 	}
782 }
783 
784 static int
785 pre_RS(DECL_ARGS)
786 {
787 	struct roffsu	 su;
788 
789 	switch (n->type) {
790 	case ROFFT_BLOCK:
791 		term_newln(p);
792 		return 1;
793 	case ROFFT_HEAD:
794 		return 0;
795 	default:
796 		break;
797 	}
798 
799 	n = n->parent->head;
800 	n->aux = SHRT_MAX + 1;
801 	if (n->child == NULL)
802 		n->aux = mt->lmargin[mt->lmargincur];
803 	else if (a2roffsu(n->child->string, &su, SCALE_EN) != NULL)
804 		n->aux = term_hen(p, &su);
805 	if (n->aux < 0 && (size_t)(-n->aux) > mt->offset)
806 		n->aux = -mt->offset;
807 	else if (n->aux > SHRT_MAX)
808 		n->aux = term_len(p, p->defindent);
809 
810 	mt->offset += n->aux;
811 	p->tcol->offset = mt->offset;
812 	p->tcol->rmargin = p->maxrmargin;
813 
814 	if (++mt->lmarginsz < MAXMARGINS)
815 		mt->lmargincur = mt->lmarginsz;
816 
817 	mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
818 	return 1;
819 }
820 
821 static void
822 post_RS(DECL_ARGS)
823 {
824 
825 	switch (n->type) {
826 	case ROFFT_BLOCK:
827 		return;
828 	case ROFFT_HEAD:
829 		return;
830 	default:
831 		term_newln(p);
832 		break;
833 	}
834 
835 	mt->offset -= n->parent->head->aux;
836 	p->tcol->offset = mt->offset;
837 
838 	if (--mt->lmarginsz < MAXMARGINS)
839 		mt->lmargincur = mt->lmarginsz;
840 }
841 
842 static int
843 pre_UR(DECL_ARGS)
844 {
845 
846 	return n->type != ROFFT_HEAD;
847 }
848 
849 static void
850 post_UR(DECL_ARGS)
851 {
852 
853 	if (n->type != ROFFT_BLOCK)
854 		return;
855 
856 	term_word(p, "<");
857 	p->flags |= TERMP_NOSPACE;
858 
859 	if (NULL != n->child->child)
860 		print_man_node(p, mt, n->child->child, meta);
861 
862 	p->flags |= TERMP_NOSPACE;
863 	term_word(p, ">");
864 }
865 
866 static void
867 print_man_node(DECL_ARGS)
868 {
869 	int		 c;
870 
871 	switch (n->type) {
872 	case ROFFT_TEXT:
873 		/*
874 		 * If we have a blank line, output a vertical space.
875 		 * If we have a space as the first character, break
876 		 * before printing the line's data.
877 		 */
878 		if (*n->string == '\0') {
879 			if (p->flags & TERMP_NONEWLINE)
880 				term_newln(p);
881 			else
882 				term_vspace(p);
883 			return;
884 		} else if (*n->string == ' ' && n->flags & NODE_LINE &&
885 		    (p->flags & TERMP_NONEWLINE) == 0)
886 			term_newln(p);
887 
888 		term_word(p, n->string);
889 		goto out;
890 	case ROFFT_COMMENT:
891 		return;
892 	case ROFFT_EQN:
893 		if ( ! (n->flags & NODE_LINE))
894 			p->flags |= TERMP_NOSPACE;
895 		term_eqn(p, n->eqn);
896 		if (n->next != NULL && ! (n->next->flags & NODE_LINE))
897 			p->flags |= TERMP_NOSPACE;
898 		return;
899 	case ROFFT_TBL:
900 		if (p->tbl.cols == NULL)
901 			term_vspace(p);
902 		term_tbl(p, n->span);
903 		return;
904 	default:
905 		break;
906 	}
907 
908 	if (n->tok < ROFF_MAX) {
909 		roff_term_pre(p, n);
910 		return;
911 	}
912 
913 	assert(n->tok >= MAN_TH && n->tok <= MAN_MAX);
914 	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
915 		term_fontrepl(p, TERMFONT_NONE);
916 
917 	c = 1;
918 	if (termacts[n->tok].pre)
919 		c = (*termacts[n->tok].pre)(p, mt, n, meta);
920 
921 	if (c && n->child)
922 		print_man_nodelist(p, mt, n->child, meta);
923 
924 	if (termacts[n->tok].post)
925 		(*termacts[n->tok].post)(p, mt, n, meta);
926 	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
927 		term_fontrepl(p, TERMFONT_NONE);
928 
929 out:
930 	/*
931 	 * If we're in a literal context, make sure that words
932 	 * together on the same line stay together.  This is a
933 	 * POST-printing call, so we check the NEXT word.  Since
934 	 * -man doesn't have nested macros, we don't need to be
935 	 * more specific than this.
936 	 */
937 	if (mt->fl & MANT_LITERAL &&
938 	    ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) &&
939 	    (n->next == NULL || n->next->flags & NODE_LINE)) {
940 		p->flags |= TERMP_BRNEVER | TERMP_NOSPACE;
941 		if (n->string != NULL && *n->string != '\0')
942 			term_flushln(p);
943 		else
944 			term_newln(p);
945 		p->flags &= ~TERMP_BRNEVER;
946 		if (p->tcol->rmargin < p->maxrmargin &&
947 		    n->parent->tok == MAN_HP) {
948 			p->tcol->offset = p->tcol->rmargin;
949 			p->tcol->rmargin = p->maxrmargin;
950 		}
951 	}
952 	if (NODE_EOS & n->flags)
953 		p->flags |= TERMP_SENTENCE;
954 }
955 
956 
957 static void
958 print_man_nodelist(DECL_ARGS)
959 {
960 
961 	while (n != NULL) {
962 		print_man_node(p, mt, n, meta);
963 		n = n->next;
964 	}
965 }
966 
967 static void
968 print_man_foot(struct termp *p, const struct roff_meta *meta)
969 {
970 	char			*title;
971 	size_t			 datelen, titlen;
972 
973 	assert(meta->title);
974 	assert(meta->msec);
975 	assert(meta->date);
976 
977 	term_fontrepl(p, TERMFONT_NONE);
978 
979 	if (meta->hasbody)
980 		term_vspace(p);
981 
982 	/*
983 	 * Temporary, undocumented option to imitate mdoc(7) output.
984 	 * In the bottom right corner, use the operating system
985 	 * instead of the title.
986 	 */
987 
988 	if ( ! p->mdocstyle) {
989 		if (meta->hasbody) {
990 			term_vspace(p);
991 			term_vspace(p);
992 		}
993 		mandoc_asprintf(&title, "%s(%s)",
994 		    meta->title, meta->msec);
995 	} else if (meta->os) {
996 		title = mandoc_strdup(meta->os);
997 	} else {
998 		title = mandoc_strdup("");
999 	}
1000 	datelen = term_strlen(p, meta->date);
1001 
1002 	/* Bottom left corner: operating system. */
1003 
1004 	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
1005 	p->trailspace = 1;
1006 	p->tcol->offset = 0;
1007 	p->tcol->rmargin = p->maxrmargin > datelen ?
1008 	    (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0;
1009 
1010 	if (meta->os)
1011 		term_word(p, meta->os);
1012 	term_flushln(p);
1013 
1014 	/* At the bottom in the middle: manual date. */
1015 
1016 	p->tcol->offset = p->tcol->rmargin;
1017 	titlen = term_strlen(p, title);
1018 	p->tcol->rmargin = p->maxrmargin > titlen ?
1019 	    p->maxrmargin - titlen : 0;
1020 	p->flags |= TERMP_NOSPACE;
1021 
1022 	term_word(p, meta->date);
1023 	term_flushln(p);
1024 
1025 	/* Bottom right corner: manual title and section. */
1026 
1027 	p->flags &= ~TERMP_NOBREAK;
1028 	p->flags |= TERMP_NOSPACE;
1029 	p->trailspace = 0;
1030 	p->tcol->offset = p->tcol->rmargin;
1031 	p->tcol->rmargin = p->maxrmargin;
1032 
1033 	term_word(p, title);
1034 	term_flushln(p);
1035 
1036 	/*
1037 	 * Reset the terminal state for more output after the footer:
1038 	 * Some output modes, in particular PostScript and PDF, print
1039 	 * the header and the footer into a buffer such that it can be
1040 	 * reused for multiple output pages, then go on to format the
1041 	 * main text.
1042 	 */
1043 
1044         p->tcol->offset = 0;
1045         p->flags = 0;
1046 
1047 	free(title);
1048 }
1049 
1050 static void
1051 print_man_head(struct termp *p, const struct roff_meta *meta)
1052 {
1053 	const char		*volume;
1054 	char			*title;
1055 	size_t			 vollen, titlen;
1056 
1057 	assert(meta->title);
1058 	assert(meta->msec);
1059 
1060 	volume = NULL == meta->vol ? "" : meta->vol;
1061 	vollen = term_strlen(p, volume);
1062 
1063 	/* Top left corner: manual title and section. */
1064 
1065 	mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec);
1066 	titlen = term_strlen(p, title);
1067 
1068 	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1069 	p->trailspace = 1;
1070 	p->tcol->offset = 0;
1071 	p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
1072 	    (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
1073 	    vollen < p->maxrmargin ? p->maxrmargin - vollen : 0;
1074 
1075 	term_word(p, title);
1076 	term_flushln(p);
1077 
1078 	/* At the top in the middle: manual volume. */
1079 
1080 	p->flags |= TERMP_NOSPACE;
1081 	p->tcol->offset = p->tcol->rmargin;
1082 	p->tcol->rmargin = p->tcol->offset + vollen + titlen <
1083 	    p->maxrmargin ?  p->maxrmargin - titlen : p->maxrmargin;
1084 
1085 	term_word(p, volume);
1086 	term_flushln(p);
1087 
1088 	/* Top right corner: title and section, again. */
1089 
1090 	p->flags &= ~TERMP_NOBREAK;
1091 	p->trailspace = 0;
1092 	if (p->tcol->rmargin + titlen <= p->maxrmargin) {
1093 		p->flags |= TERMP_NOSPACE;
1094 		p->tcol->offset = p->tcol->rmargin;
1095 		p->tcol->rmargin = p->maxrmargin;
1096 		term_word(p, title);
1097 		term_flushln(p);
1098 	}
1099 
1100 	p->flags &= ~TERMP_NOSPACE;
1101 	p->tcol->offset = 0;
1102 	p->tcol->rmargin = p->maxrmargin;
1103 
1104 	/*
1105 	 * Groff prints three blank lines before the content.
1106 	 * Do the same, except in the temporary, undocumented
1107 	 * mode imitating mdoc(7) output.
1108 	 */
1109 
1110 	term_vspace(p);
1111 	if ( ! p->mdocstyle) {
1112 		term_vspace(p);
1113 		term_vspace(p);
1114 	}
1115 	free(title);
1116 }
1117