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