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