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