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