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