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