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