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