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