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