1/*	$Id: mdoc_term.c,v 1.238 2011/11/13 13:15:14 schwarze Exp $ */
2/*
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010 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 <stdint.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30
31#include "mandoc.h"
32#include "out.h"
33#include "term.h"
34#include "mdoc.h"
35#include "main.h"
36
37struct	termpair {
38	struct termpair	 *ppair;
39	int		  count;
40};
41
42#define	DECL_ARGS struct termp *p, \
43		  struct termpair *pair, \
44	  	  const struct mdoc_meta *m, \
45		  const struct mdoc_node *n
46
47struct	termact {
48	int	(*pre)(DECL_ARGS);
49	void	(*post)(DECL_ARGS);
50};
51
52static	size_t	  a2width(const struct termp *, const char *);
53static	size_t	  a2height(const struct termp *, const char *);
54static	size_t	  a2offs(const struct termp *, const char *);
55
56static	void	  print_bvspace(struct termp *,
57			const struct mdoc_node *,
58			const struct mdoc_node *);
59static	void  	  print_mdoc_node(DECL_ARGS);
60static	void	  print_mdoc_nodelist(DECL_ARGS);
61static	void	  print_mdoc_head(struct termp *, const void *);
62static	void	  print_mdoc_foot(struct termp *, const void *);
63static	void	  synopsis_pre(struct termp *,
64			const struct mdoc_node *);
65
66static	void	  termp____post(DECL_ARGS);
67static	void	  termp__t_post(DECL_ARGS);
68static	void	  termp_an_post(DECL_ARGS);
69static	void	  termp_bd_post(DECL_ARGS);
70static	void	  termp_bk_post(DECL_ARGS);
71static	void	  termp_bl_post(DECL_ARGS);
72static	void	  termp_d1_post(DECL_ARGS);
73static	void	  termp_fo_post(DECL_ARGS);
74static	void	  termp_in_post(DECL_ARGS);
75static	void	  termp_it_post(DECL_ARGS);
76static	void	  termp_lb_post(DECL_ARGS);
77static	void	  termp_nm_post(DECL_ARGS);
78static	void	  termp_pf_post(DECL_ARGS);
79static	void	  termp_quote_post(DECL_ARGS);
80static	void	  termp_sh_post(DECL_ARGS);
81static	void	  termp_ss_post(DECL_ARGS);
82
83static	int	  termp__a_pre(DECL_ARGS);
84static	int	  termp__t_pre(DECL_ARGS);
85static	int	  termp_an_pre(DECL_ARGS);
86static	int	  termp_ap_pre(DECL_ARGS);
87static	int	  termp_bd_pre(DECL_ARGS);
88static	int	  termp_bf_pre(DECL_ARGS);
89static	int	  termp_bk_pre(DECL_ARGS);
90static	int	  termp_bl_pre(DECL_ARGS);
91static	int	  termp_bold_pre(DECL_ARGS);
92static	int	  termp_bt_pre(DECL_ARGS);
93static	int	  termp_bx_pre(DECL_ARGS);
94static	int	  termp_cd_pre(DECL_ARGS);
95static	int	  termp_d1_pre(DECL_ARGS);
96static	int	  termp_ex_pre(DECL_ARGS);
97static	int	  termp_fa_pre(DECL_ARGS);
98static	int	  termp_fd_pre(DECL_ARGS);
99static	int	  termp_fl_pre(DECL_ARGS);
100static	int	  termp_fn_pre(DECL_ARGS);
101static	int	  termp_fo_pre(DECL_ARGS);
102static	int	  termp_ft_pre(DECL_ARGS);
103static	int	  termp_igndelim_pre(DECL_ARGS);
104static	int	  termp_in_pre(DECL_ARGS);
105static	int	  termp_it_pre(DECL_ARGS);
106static	int	  termp_li_pre(DECL_ARGS);
107static	int	  termp_lk_pre(DECL_ARGS);
108static	int	  termp_nd_pre(DECL_ARGS);
109static	int	  termp_nm_pre(DECL_ARGS);
110static	int	  termp_ns_pre(DECL_ARGS);
111static	int	  termp_quote_pre(DECL_ARGS);
112static	int	  termp_rs_pre(DECL_ARGS);
113static	int	  termp_rv_pre(DECL_ARGS);
114static	int	  termp_sh_pre(DECL_ARGS);
115static	int	  termp_sm_pre(DECL_ARGS);
116static	int	  termp_sp_pre(DECL_ARGS);
117static	int	  termp_ss_pre(DECL_ARGS);
118static	int	  termp_under_pre(DECL_ARGS);
119static	int	  termp_ud_pre(DECL_ARGS);
120static	int	  termp_vt_pre(DECL_ARGS);
121static	int	  termp_xr_pre(DECL_ARGS);
122static	int	  termp_xx_pre(DECL_ARGS);
123
124static	const struct termact termacts[MDOC_MAX] = {
125	{ termp_ap_pre, NULL }, /* Ap */
126	{ NULL, NULL }, /* Dd */
127	{ NULL, NULL }, /* Dt */
128	{ NULL, NULL }, /* Os */
129	{ termp_sh_pre, termp_sh_post }, /* Sh */
130	{ termp_ss_pre, termp_ss_post }, /* Ss */
131	{ termp_sp_pre, NULL }, /* Pp */
132	{ termp_d1_pre, termp_d1_post }, /* D1 */
133	{ termp_d1_pre, termp_d1_post }, /* Dl */
134	{ termp_bd_pre, termp_bd_post }, /* Bd */
135	{ NULL, NULL }, /* Ed */
136	{ termp_bl_pre, termp_bl_post }, /* Bl */
137	{ NULL, NULL }, /* El */
138	{ termp_it_pre, termp_it_post }, /* It */
139	{ termp_under_pre, NULL }, /* Ad */
140	{ termp_an_pre, termp_an_post }, /* An */
141	{ termp_under_pre, NULL }, /* Ar */
142	{ termp_cd_pre, NULL }, /* Cd */
143	{ termp_bold_pre, NULL }, /* Cm */
144	{ NULL, NULL }, /* Dv */
145	{ NULL, NULL }, /* Er */
146	{ NULL, NULL }, /* Ev */
147	{ termp_ex_pre, NULL }, /* Ex */
148	{ termp_fa_pre, NULL }, /* Fa */
149	{ termp_fd_pre, NULL }, /* Fd */
150	{ termp_fl_pre, NULL }, /* Fl */
151	{ termp_fn_pre, NULL }, /* Fn */
152	{ termp_ft_pre, NULL }, /* Ft */
153	{ termp_bold_pre, NULL }, /* Ic */
154	{ termp_in_pre, termp_in_post }, /* In */
155	{ termp_li_pre, NULL }, /* Li */
156	{ termp_nd_pre, NULL }, /* Nd */
157	{ termp_nm_pre, termp_nm_post }, /* Nm */
158	{ termp_quote_pre, termp_quote_post }, /* Op */
159	{ NULL, NULL }, /* Ot */
160	{ termp_under_pre, NULL }, /* Pa */
161	{ termp_rv_pre, NULL }, /* Rv */
162	{ NULL, NULL }, /* St */
163	{ termp_under_pre, NULL }, /* Va */
164	{ termp_vt_pre, NULL }, /* Vt */
165	{ termp_xr_pre, NULL }, /* Xr */
166	{ termp__a_pre, termp____post }, /* %A */
167	{ termp_under_pre, termp____post }, /* %B */
168	{ NULL, termp____post }, /* %D */
169	{ termp_under_pre, termp____post }, /* %I */
170	{ termp_under_pre, termp____post }, /* %J */
171	{ NULL, termp____post }, /* %N */
172	{ NULL, termp____post }, /* %O */
173	{ NULL, termp____post }, /* %P */
174	{ NULL, termp____post }, /* %R */
175	{ termp__t_pre, termp__t_post }, /* %T */
176	{ NULL, termp____post }, /* %V */
177	{ NULL, NULL }, /* Ac */
178	{ termp_quote_pre, termp_quote_post }, /* Ao */
179	{ termp_quote_pre, termp_quote_post }, /* Aq */
180	{ NULL, NULL }, /* At */
181	{ NULL, NULL }, /* Bc */
182	{ termp_bf_pre, NULL }, /* Bf */
183	{ termp_quote_pre, termp_quote_post }, /* Bo */
184	{ termp_quote_pre, termp_quote_post }, /* Bq */
185	{ termp_xx_pre, NULL }, /* Bsx */
186	{ termp_bx_pre, NULL }, /* Bx */
187	{ NULL, NULL }, /* Db */
188	{ NULL, NULL }, /* Dc */
189	{ termp_quote_pre, termp_quote_post }, /* Do */
190	{ termp_quote_pre, termp_quote_post }, /* Dq */
191	{ NULL, NULL }, /* Ec */ /* FIXME: no space */
192	{ NULL, NULL }, /* Ef */
193	{ termp_under_pre, NULL }, /* Em */
194	{ termp_quote_pre, termp_quote_post }, /* Eo */
195	{ termp_xx_pre, NULL }, /* Fx */
196	{ termp_bold_pre, NULL }, /* Ms */
197	{ termp_igndelim_pre, NULL }, /* No */
198	{ termp_ns_pre, NULL }, /* Ns */
199	{ termp_xx_pre, NULL }, /* Nx */
200	{ termp_xx_pre, NULL }, /* Ox */
201	{ NULL, NULL }, /* Pc */
202	{ termp_igndelim_pre, termp_pf_post }, /* Pf */
203	{ termp_quote_pre, termp_quote_post }, /* Po */
204	{ termp_quote_pre, termp_quote_post }, /* Pq */
205	{ NULL, NULL }, /* Qc */
206	{ termp_quote_pre, termp_quote_post }, /* Ql */
207	{ termp_quote_pre, termp_quote_post }, /* Qo */
208	{ termp_quote_pre, termp_quote_post }, /* Qq */
209	{ NULL, NULL }, /* Re */
210	{ termp_rs_pre, NULL }, /* Rs */
211	{ NULL, NULL }, /* Sc */
212	{ termp_quote_pre, termp_quote_post }, /* So */
213	{ termp_quote_pre, termp_quote_post }, /* Sq */
214	{ termp_sm_pre, NULL }, /* Sm */
215	{ termp_under_pre, NULL }, /* Sx */
216	{ termp_bold_pre, NULL }, /* Sy */
217	{ NULL, NULL }, /* Tn */
218	{ termp_xx_pre, NULL }, /* Ux */
219	{ NULL, NULL }, /* Xc */
220	{ NULL, NULL }, /* Xo */
221	{ termp_fo_pre, termp_fo_post }, /* Fo */
222	{ NULL, NULL }, /* Fc */
223	{ termp_quote_pre, termp_quote_post }, /* Oo */
224	{ NULL, NULL }, /* Oc */
225	{ termp_bk_pre, termp_bk_post }, /* Bk */
226	{ NULL, NULL }, /* Ek */
227	{ termp_bt_pre, NULL }, /* Bt */
228	{ NULL, NULL }, /* Hf */
229	{ NULL, NULL }, /* Fr */
230	{ termp_ud_pre, NULL }, /* Ud */
231	{ NULL, termp_lb_post }, /* Lb */
232	{ termp_sp_pre, NULL }, /* Lp */
233	{ termp_lk_pre, NULL }, /* Lk */
234	{ termp_under_pre, NULL }, /* Mt */
235	{ termp_quote_pre, termp_quote_post }, /* Brq */
236	{ termp_quote_pre, termp_quote_post }, /* Bro */
237	{ NULL, NULL }, /* Brc */
238	{ NULL, termp____post }, /* %C */
239	{ NULL, NULL }, /* Es */ /* TODO */
240	{ NULL, NULL }, /* En */ /* TODO */
241	{ termp_xx_pre, NULL }, /* Dx */
242	{ NULL, termp____post }, /* %Q */
243	{ termp_sp_pre, NULL }, /* br */
244	{ termp_sp_pre, NULL }, /* sp */
245	{ termp_under_pre, termp____post }, /* %U */
246	{ NULL, NULL }, /* Ta */
247};
248
249
250void
251terminal_mdoc(void *arg, const struct mdoc *mdoc)
252{
253	const struct mdoc_node	*n;
254	const struct mdoc_meta	*m;
255	struct termp		*p;
256
257	p = (struct termp *)arg;
258
259	if (0 == p->defindent)
260		p->defindent = 5;
261
262	p->overstep = 0;
263	p->maxrmargin = p->defrmargin;
264	p->tabwidth = term_len(p, 5);
265
266	if (NULL == p->symtab)
267		p->symtab = mchars_alloc();
268
269	n = mdoc_node(mdoc);
270	m = mdoc_meta(mdoc);
271
272	term_begin(p, print_mdoc_head, print_mdoc_foot, m);
273
274	if (n->child)
275		print_mdoc_nodelist(p, NULL, m, n->child);
276
277	term_end(p);
278}
279
280
281static void
282print_mdoc_nodelist(DECL_ARGS)
283{
284
285	print_mdoc_node(p, pair, m, n);
286	if (n->next)
287		print_mdoc_nodelist(p, pair, m, n->next);
288}
289
290
291/* ARGSUSED */
292static void
293print_mdoc_node(DECL_ARGS)
294{
295	int		 chld;
296	const void	*font;
297	struct termpair	 npair;
298	size_t		 offset, rmargin;
299
300	chld = 1;
301	offset = p->offset;
302	rmargin = p->rmargin;
303	font = term_fontq(p);
304
305	memset(&npair, 0, sizeof(struct termpair));
306	npair.ppair = pair;
307
308	/*
309	 * Keeps only work until the end of a line.  If a keep was
310	 * invoked in a prior line, revert it to PREKEEP.
311	 *
312	 * Also let SYNPRETTY sections behave as if they were wrapped
313	 * in a `Bk' block.
314	 */
315
316	if (TERMP_KEEP & p->flags || MDOC_SYNPRETTY & n->flags) {
317		if (n->prev && n->prev->line != n->line) {
318			p->flags &= ~TERMP_KEEP;
319			p->flags |= TERMP_PREKEEP;
320		} else if (NULL == n->prev) {
321			if (n->parent && n->parent->line != n->line) {
322				p->flags &= ~TERMP_KEEP;
323				p->flags |= TERMP_PREKEEP;
324			}
325		}
326	}
327
328	/*
329	 * Since SYNPRETTY sections aren't "turned off" with `Ek',
330	 * we have to intuit whether we should disable formatting.
331	 */
332
333	if ( ! (MDOC_SYNPRETTY & n->flags) &&
334	    ((n->prev   && MDOC_SYNPRETTY & n->prev->flags) ||
335	     (n->parent && MDOC_SYNPRETTY & n->parent->flags)))
336		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
337
338	/*
339	 * After the keep flags have been set up, we may now
340	 * produce output.  Note that some pre-handlers do so.
341	 */
342
343	switch (n->type) {
344	case (MDOC_TEXT):
345		if (' ' == *n->string && MDOC_LINE & n->flags)
346			term_newln(p);
347		if (MDOC_DELIMC & n->flags)
348			p->flags |= TERMP_NOSPACE;
349		term_word(p, n->string);
350		if (MDOC_DELIMO & n->flags)
351			p->flags |= TERMP_NOSPACE;
352		break;
353	case (MDOC_EQN):
354		term_eqn(p, n->eqn);
355		break;
356	case (MDOC_TBL):
357		term_tbl(p, n->span);
358		break;
359	default:
360		if (termacts[n->tok].pre && ENDBODY_NOT == n->end)
361			chld = (*termacts[n->tok].pre)
362				(p, &npair, m, n);
363		break;
364	}
365
366	if (chld && n->child)
367		print_mdoc_nodelist(p, &npair, m, n->child);
368
369	term_fontpopq(p, font);
370
371	switch (n->type) {
372	case (MDOC_TEXT):
373		break;
374	case (MDOC_TBL):
375		break;
376	case (MDOC_EQN):
377		break;
378	default:
379		if ( ! termacts[n->tok].post || MDOC_ENDED & n->flags)
380			break;
381		(void)(*termacts[n->tok].post)(p, &npair, m, n);
382
383		/*
384		 * Explicit end tokens not only call the post
385		 * handler, but also tell the respective block
386		 * that it must not call the post handler again.
387		 */
388		if (ENDBODY_NOT != n->end)
389			n->pending->flags |= MDOC_ENDED;
390
391		/*
392		 * End of line terminating an implicit block
393		 * while an explicit block is still open.
394		 * Continue the explicit block without spacing.
395		 */
396		if (ENDBODY_NOSPACE == n->end)
397			p->flags |= TERMP_NOSPACE;
398		break;
399	}
400
401	if (MDOC_EOS & n->flags)
402		p->flags |= TERMP_SENTENCE;
403
404	p->offset = offset;
405	p->rmargin = rmargin;
406}
407
408
409static void
410print_mdoc_foot(struct termp *p, const void *arg)
411{
412	const struct mdoc_meta *m;
413
414	m = (const struct mdoc_meta *)arg;
415
416	term_fontrepl(p, TERMFONT_NONE);
417
418	/*
419	 * Output the footer in new-groff style, that is, three columns
420	 * with the middle being the manual date and flanking columns
421	 * being the operating system:
422	 *
423	 * SYSTEM                  DATE                    SYSTEM
424	 */
425
426	term_vspace(p);
427
428	p->offset = 0;
429	p->rmargin = (p->maxrmargin -
430			term_strlen(p, m->date) + term_len(p, 1)) / 2;
431	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
432
433	term_word(p, m->os);
434	term_flushln(p);
435
436	p->offset = p->rmargin;
437	p->rmargin = p->maxrmargin - term_strlen(p, m->os);
438	p->flags |= TERMP_NOSPACE;
439
440	term_word(p, m->date);
441	term_flushln(p);
442
443	p->offset = p->rmargin;
444	p->rmargin = p->maxrmargin;
445	p->flags &= ~TERMP_NOBREAK;
446	p->flags |= TERMP_NOSPACE;
447
448	term_word(p, m->os);
449	term_flushln(p);
450
451	p->offset = 0;
452	p->rmargin = p->maxrmargin;
453	p->flags = 0;
454}
455
456
457static void
458print_mdoc_head(struct termp *p, const void *arg)
459{
460	char		buf[BUFSIZ], title[BUFSIZ];
461	size_t		buflen, titlen;
462	const struct mdoc_meta *m;
463
464	m = (const struct mdoc_meta *)arg;
465
466	/*
467	 * The header is strange.  It has three components, which are
468	 * really two with the first duplicated.  It goes like this:
469	 *
470	 * IDENTIFIER              TITLE                   IDENTIFIER
471	 *
472	 * The IDENTIFIER is NAME(SECTION), which is the command-name
473	 * (if given, or "unknown" if not) followed by the manual page
474	 * section.  These are given in `Dt'.  The TITLE is a free-form
475	 * string depending on the manual volume.  If not specified, it
476	 * switches on the manual section.
477	 */
478
479	p->offset = 0;
480	p->rmargin = p->maxrmargin;
481
482	assert(m->vol);
483	strlcpy(buf, m->vol, BUFSIZ);
484	buflen = term_strlen(p, buf);
485
486	if (m->arch) {
487		strlcat(buf, " (", BUFSIZ);
488		strlcat(buf, m->arch, BUFSIZ);
489		strlcat(buf, ")", BUFSIZ);
490	}
491
492	snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec);
493	titlen = term_strlen(p, title);
494
495	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
496	p->offset = 0;
497	p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ?
498	    (p->maxrmargin -
499	     term_strlen(p, buf) + term_len(p, 1)) / 2 :
500	    p->maxrmargin - buflen;
501
502	term_word(p, title);
503	term_flushln(p);
504
505	p->flags |= TERMP_NOSPACE;
506	p->offset = p->rmargin;
507	p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
508	    p->maxrmargin - titlen : p->maxrmargin;
509
510	term_word(p, buf);
511	term_flushln(p);
512
513	p->flags &= ~TERMP_NOBREAK;
514	if (p->rmargin + titlen <= p->maxrmargin) {
515		p->flags |= TERMP_NOSPACE;
516		p->offset = p->rmargin;
517		p->rmargin = p->maxrmargin;
518		term_word(p, title);
519		term_flushln(p);
520	}
521
522	p->flags &= ~TERMP_NOSPACE;
523	p->offset = 0;
524	p->rmargin = p->maxrmargin;
525}
526
527
528static size_t
529a2height(const struct termp *p, const char *v)
530{
531	struct roffsu	 su;
532
533
534	assert(v);
535	if ( ! a2roffsu(v, &su, SCALE_VS))
536		SCALE_VS_INIT(&su, atoi(v));
537
538	return(term_vspan(p, &su));
539}
540
541
542static size_t
543a2width(const struct termp *p, const char *v)
544{
545	struct roffsu	 su;
546
547	assert(v);
548	if ( ! a2roffsu(v, &su, SCALE_MAX))
549		SCALE_HS_INIT(&su, term_strlen(p, v));
550
551	return(term_hspan(p, &su));
552}
553
554
555static size_t
556a2offs(const struct termp *p, const char *v)
557{
558	struct roffsu	 su;
559
560	if ('\0' == *v)
561		return(0);
562	else if (0 == strcmp(v, "left"))
563		return(0);
564	else if (0 == strcmp(v, "indent"))
565		return(term_len(p, p->defindent + 1));
566	else if (0 == strcmp(v, "indent-two"))
567		return(term_len(p, (p->defindent + 1) * 2));
568	else if ( ! a2roffsu(v, &su, SCALE_MAX))
569		SCALE_HS_INIT(&su, term_strlen(p, v));
570
571	return(term_hspan(p, &su));
572}
573
574
575/*
576 * Determine how much space to print out before block elements of `It'
577 * (and thus `Bl') and `Bd'.  And then go ahead and print that space,
578 * too.
579 */
580static void
581print_bvspace(struct termp *p,
582		const struct mdoc_node *bl,
583		const struct mdoc_node *n)
584{
585	const struct mdoc_node	*nn;
586
587	assert(n);
588
589	term_newln(p);
590
591	if (MDOC_Bd == bl->tok && bl->norm->Bd.comp)
592		return;
593	if (MDOC_Bl == bl->tok && bl->norm->Bl.comp)
594		return;
595
596	/* Do not vspace directly after Ss/Sh. */
597
598	for (nn = n; nn; nn = nn->parent) {
599		if (MDOC_BLOCK != nn->type)
600			continue;
601		if (MDOC_Ss == nn->tok)
602			return;
603		if (MDOC_Sh == nn->tok)
604			return;
605		if (NULL == nn->prev)
606			continue;
607		break;
608	}
609
610	/* A `-column' does not assert vspace within the list. */
611
612	if (MDOC_Bl == bl->tok && LIST_column == bl->norm->Bl.type)
613		if (n->prev && MDOC_It == n->prev->tok)
614			return;
615
616	/* A `-diag' without body does not vspace. */
617
618	if (MDOC_Bl == bl->tok && LIST_diag == bl->norm->Bl.type)
619		if (n->prev && MDOC_It == n->prev->tok) {
620			assert(n->prev->body);
621			if (NULL == n->prev->body->child)
622				return;
623		}
624
625	term_vspace(p);
626}
627
628
629/* ARGSUSED */
630static int
631termp_it_pre(DECL_ARGS)
632{
633	const struct mdoc_node *bl, *nn;
634	char		        buf[7];
635	int		        i;
636	size_t		        width, offset, ncols, dcol;
637	enum mdoc_list		type;
638
639	if (MDOC_BLOCK == n->type) {
640		print_bvspace(p, n->parent->parent, n);
641		return(1);
642	}
643
644	bl = n->parent->parent->parent;
645	type = bl->norm->Bl.type;
646
647	/*
648	 * First calculate width and offset.  This is pretty easy unless
649	 * we're a -column list, in which case all prior columns must
650	 * be accounted for.
651	 */
652
653	width = offset = 0;
654
655	if (bl->norm->Bl.offs)
656		offset = a2offs(p, bl->norm->Bl.offs);
657
658	switch (type) {
659	case (LIST_column):
660		if (MDOC_HEAD == n->type)
661			break;
662
663		/*
664		 * Imitate groff's column handling:
665		 * - For each earlier column, add its width.
666		 * - For less than 5 columns, add four more blanks per
667		 *   column.
668		 * - For exactly 5 columns, add three more blank per
669		 *   column.
670		 * - For more than 5 columns, add only one column.
671		 */
672		ncols = bl->norm->Bl.ncols;
673
674		/* LINTED */
675		dcol = ncols < 5 ? term_len(p, 4) :
676			ncols == 5 ? term_len(p, 3) : term_len(p, 1);
677
678		/*
679		 * Calculate the offset by applying all prior MDOC_BODY,
680		 * so we stop at the MDOC_HEAD (NULL == nn->prev).
681		 */
682
683		for (i = 0, nn = n->prev;
684				nn->prev && i < (int)ncols;
685				nn = nn->prev, i++)
686			offset += dcol + a2width
687				(p, bl->norm->Bl.cols[i]);
688
689		/*
690		 * When exceeding the declared number of columns, leave
691		 * the remaining widths at 0.  This will later be
692		 * adjusted to the default width of 10, or, for the last
693		 * column, stretched to the right margin.
694		 */
695		if (i >= (int)ncols)
696			break;
697
698		/*
699		 * Use the declared column widths, extended as explained
700		 * in the preceding paragraph.
701		 */
702		width = a2width(p, bl->norm->Bl.cols[i]) + dcol;
703		break;
704	default:
705		if (NULL == bl->norm->Bl.width)
706			break;
707
708		/*
709		 * Note: buffer the width by 2, which is groff's magic
710		 * number for buffering single arguments.  See the above
711		 * handling for column for how this changes.
712		 */
713		assert(bl->norm->Bl.width);
714		width = a2width(p, bl->norm->Bl.width) + term_len(p, 2);
715		break;
716	}
717
718	/*
719	 * List-type can override the width in the case of fixed-head
720	 * values (bullet, dash/hyphen, enum).  Tags need a non-zero
721	 * offset.
722	 */
723
724	switch (type) {
725	case (LIST_bullet):
726		/* FALLTHROUGH */
727	case (LIST_dash):
728		/* FALLTHROUGH */
729	case (LIST_hyphen):
730		if (width < term_len(p, 4))
731			width = term_len(p, 4);
732		break;
733	case (LIST_enum):
734		if (width < term_len(p, 5))
735			width = term_len(p, 5);
736		break;
737	case (LIST_hang):
738		if (0 == width)
739			width = term_len(p, 8);
740		break;
741	case (LIST_column):
742		/* FALLTHROUGH */
743	case (LIST_tag):
744		if (0 == width)
745			width = term_len(p, 10);
746		break;
747	default:
748		break;
749	}
750
751	/*
752	 * Whitespace control.  Inset bodies need an initial space,
753	 * while diagonal bodies need two.
754	 */
755
756	p->flags |= TERMP_NOSPACE;
757
758	switch (type) {
759	case (LIST_diag):
760		if (MDOC_BODY == n->type)
761			term_word(p, "\\ \\ ");
762		break;
763	case (LIST_inset):
764		if (MDOC_BODY == n->type)
765			term_word(p, "\\ ");
766		break;
767	default:
768		break;
769	}
770
771	p->flags |= TERMP_NOSPACE;
772
773	switch (type) {
774	case (LIST_diag):
775		if (MDOC_HEAD == n->type)
776			term_fontpush(p, TERMFONT_BOLD);
777		break;
778	default:
779		break;
780	}
781
782	/*
783	 * Pad and break control.  This is the tricky part.  These flags
784	 * are documented in term_flushln() in term.c.  Note that we're
785	 * going to unset all of these flags in termp_it_post() when we
786	 * exit.
787	 */
788
789	switch (type) {
790	case (LIST_bullet):
791		/* FALLTHROUGH */
792	case (LIST_dash):
793		/* FALLTHROUGH */
794	case (LIST_enum):
795		/* FALLTHROUGH */
796	case (LIST_hyphen):
797		if (MDOC_HEAD == n->type)
798			p->flags |= TERMP_NOBREAK;
799		break;
800	case (LIST_hang):
801		if (MDOC_HEAD == n->type)
802			p->flags |= TERMP_NOBREAK;
803		else
804			break;
805
806		/*
807		 * This is ugly.  If `-hang' is specified and the body
808		 * is a `Bl' or `Bd', then we want basically to nullify
809		 * the "overstep" effect in term_flushln() and treat
810		 * this as a `-ohang' list instead.
811		 */
812		if (n->next->child &&
813				(MDOC_Bl == n->next->child->tok ||
814				 MDOC_Bd == n->next->child->tok))
815			p->flags &= ~TERMP_NOBREAK;
816		else
817			p->flags |= TERMP_HANG;
818		break;
819	case (LIST_tag):
820		if (MDOC_HEAD == n->type)
821			p->flags |= TERMP_NOBREAK | TERMP_TWOSPACE;
822
823		if (MDOC_HEAD != n->type)
824			break;
825		if (NULL == n->next || NULL == n->next->child)
826			p->flags |= TERMP_DANGLE;
827		break;
828	case (LIST_column):
829		if (MDOC_HEAD == n->type)
830			break;
831
832		if (NULL == n->next)
833			p->flags &= ~TERMP_NOBREAK;
834		else
835			p->flags |= TERMP_NOBREAK;
836
837		break;
838	case (LIST_diag):
839		if (MDOC_HEAD == n->type)
840			p->flags |= TERMP_NOBREAK;
841		break;
842	default:
843		break;
844	}
845
846	/*
847	 * Margin control.  Set-head-width lists have their right
848	 * margins shortened.  The body for these lists has the offset
849	 * necessarily lengthened.  Everybody gets the offset.
850	 */
851
852	p->offset += offset;
853
854	switch (type) {
855	case (LIST_hang):
856		/*
857		 * Same stipulation as above, regarding `-hang'.  We
858		 * don't want to recalculate rmargin and offsets when
859		 * using `Bd' or `Bl' within `-hang' overstep lists.
860		 */
861		if (MDOC_HEAD == n->type && n->next->child &&
862				(MDOC_Bl == n->next->child->tok ||
863				 MDOC_Bd == n->next->child->tok))
864			break;
865		/* FALLTHROUGH */
866	case (LIST_bullet):
867		/* FALLTHROUGH */
868	case (LIST_dash):
869		/* FALLTHROUGH */
870	case (LIST_enum):
871		/* FALLTHROUGH */
872	case (LIST_hyphen):
873		/* FALLTHROUGH */
874	case (LIST_tag):
875		assert(width);
876		if (MDOC_HEAD == n->type)
877			p->rmargin = p->offset + width;
878		else
879			p->offset += width;
880		break;
881	case (LIST_column):
882		assert(width);
883		p->rmargin = p->offset + width;
884		/*
885		 * XXX - this behaviour is not documented: the
886		 * right-most column is filled to the right margin.
887		 */
888		if (MDOC_HEAD == n->type)
889			break;
890		if (NULL == n->next && p->rmargin < p->maxrmargin)
891			p->rmargin = p->maxrmargin;
892		break;
893	default:
894		break;
895	}
896
897	/*
898	 * The dash, hyphen, bullet and enum lists all have a special
899	 * HEAD character (temporarily bold, in some cases).
900	 */
901
902	if (MDOC_HEAD == n->type)
903		switch (type) {
904		case (LIST_bullet):
905			term_fontpush(p, TERMFONT_BOLD);
906			term_word(p, "\\[bu]");
907			term_fontpop(p);
908			break;
909		case (LIST_dash):
910			/* FALLTHROUGH */
911		case (LIST_hyphen):
912			term_fontpush(p, TERMFONT_BOLD);
913			term_word(p, "\\(hy");
914			term_fontpop(p);
915			break;
916		case (LIST_enum):
917			(pair->ppair->ppair->count)++;
918			snprintf(buf, sizeof(buf), "%d.",
919					pair->ppair->ppair->count);
920			term_word(p, buf);
921			break;
922		default:
923			break;
924		}
925
926	/*
927	 * If we're not going to process our children, indicate so here.
928	 */
929
930	switch (type) {
931	case (LIST_bullet):
932		/* FALLTHROUGH */
933	case (LIST_item):
934		/* FALLTHROUGH */
935	case (LIST_dash):
936		/* FALLTHROUGH */
937	case (LIST_hyphen):
938		/* FALLTHROUGH */
939	case (LIST_enum):
940		if (MDOC_HEAD == n->type)
941			return(0);
942		break;
943	case (LIST_column):
944		if (MDOC_HEAD == n->type)
945			return(0);
946		break;
947	default:
948		break;
949	}
950
951	return(1);
952}
953
954
955/* ARGSUSED */
956static void
957termp_it_post(DECL_ARGS)
958{
959	enum mdoc_list	   type;
960
961	if (MDOC_BLOCK == n->type)
962		return;
963
964	type = n->parent->parent->parent->norm->Bl.type;
965
966	switch (type) {
967	case (LIST_item):
968		/* FALLTHROUGH */
969	case (LIST_diag):
970		/* FALLTHROUGH */
971	case (LIST_inset):
972		if (MDOC_BODY == n->type)
973			term_newln(p);
974		break;
975	case (LIST_column):
976		if (MDOC_BODY == n->type)
977			term_flushln(p);
978		break;
979	default:
980		term_newln(p);
981		break;
982	}
983
984	/*
985	 * Now that our output is flushed, we can reset our tags.  Since
986	 * only `It' sets these flags, we're free to assume that nobody
987	 * has munged them in the meanwhile.
988	 */
989
990	p->flags &= ~TERMP_DANGLE;
991	p->flags &= ~TERMP_NOBREAK;
992	p->flags &= ~TERMP_TWOSPACE;
993	p->flags &= ~TERMP_HANG;
994}
995
996
997/* ARGSUSED */
998static int
999termp_nm_pre(DECL_ARGS)
1000{
1001
1002	if (MDOC_BLOCK == n->type)
1003		return(1);
1004
1005	if (MDOC_BODY == n->type) {
1006		if (NULL == n->child)
1007			return(0);
1008		p->flags |= TERMP_NOSPACE;
1009		p->offset += term_len(p, 1) +
1010		    (NULL == n->prev->child ? term_strlen(p, m->name) :
1011		     MDOC_TEXT == n->prev->child->type ?
1012			term_strlen(p, n->prev->child->string) :
1013		     term_len(p, 5));
1014		return(1);
1015	}
1016
1017	if (NULL == n->child && NULL == m->name)
1018		return(0);
1019
1020	if (MDOC_HEAD == n->type)
1021		synopsis_pre(p, n->parent);
1022
1023	if (MDOC_HEAD == n->type && n->next->child) {
1024		p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
1025		p->rmargin = p->offset + term_len(p, 1);
1026		if (NULL == n->child) {
1027			p->rmargin += term_strlen(p, m->name);
1028		} else if (MDOC_TEXT == n->child->type) {
1029			p->rmargin += term_strlen(p, n->child->string);
1030			if (n->child->next)
1031				p->flags |= TERMP_HANG;
1032		} else {
1033			p->rmargin += term_len(p, 5);
1034			p->flags |= TERMP_HANG;
1035		}
1036	}
1037
1038	term_fontpush(p, TERMFONT_BOLD);
1039	if (NULL == n->child)
1040		term_word(p, m->name);
1041	return(1);
1042}
1043
1044
1045/* ARGSUSED */
1046static void
1047termp_nm_post(DECL_ARGS)
1048{
1049
1050	if (MDOC_HEAD == n->type && n->next->child) {
1051		term_flushln(p);
1052		p->flags &= ~(TERMP_NOBREAK | TERMP_HANG);
1053	} else if (MDOC_BODY == n->type && n->child)
1054		term_flushln(p);
1055}
1056
1057
1058/* ARGSUSED */
1059static int
1060termp_fl_pre(DECL_ARGS)
1061{
1062
1063	term_fontpush(p, TERMFONT_BOLD);
1064	term_word(p, "\\-");
1065
1066	if (n->child)
1067		p->flags |= TERMP_NOSPACE;
1068	else if (n->next && n->next->line == n->line)
1069		p->flags |= TERMP_NOSPACE;
1070
1071	return(1);
1072}
1073
1074
1075/* ARGSUSED */
1076static int
1077termp__a_pre(DECL_ARGS)
1078{
1079
1080	if (n->prev && MDOC__A == n->prev->tok)
1081		if (NULL == n->next || MDOC__A != n->next->tok)
1082			term_word(p, "and");
1083
1084	return(1);
1085}
1086
1087
1088/* ARGSUSED */
1089static int
1090termp_an_pre(DECL_ARGS)
1091{
1092
1093	if (NULL == n->child)
1094		return(1);
1095
1096	/*
1097	 * If not in the AUTHORS section, `An -split' will cause
1098	 * newlines to occur before the author name.  If in the AUTHORS
1099	 * section, by default, the first `An' invocation is nosplit,
1100	 * then all subsequent ones, regardless of whether interspersed
1101	 * with other macros/text, are split.  -split, in this case,
1102	 * will override the condition of the implied first -nosplit.
1103	 */
1104
1105	if (n->sec == SEC_AUTHORS) {
1106		if ( ! (TERMP_ANPREC & p->flags)) {
1107			if (TERMP_SPLIT & p->flags)
1108				term_newln(p);
1109			return(1);
1110		}
1111		if (TERMP_NOSPLIT & p->flags)
1112			return(1);
1113		term_newln(p);
1114		return(1);
1115	}
1116
1117	if (TERMP_SPLIT & p->flags)
1118		term_newln(p);
1119
1120	return(1);
1121}
1122
1123
1124/* ARGSUSED */
1125static void
1126termp_an_post(DECL_ARGS)
1127{
1128
1129	if (n->child) {
1130		if (SEC_AUTHORS == n->sec)
1131			p->flags |= TERMP_ANPREC;
1132		return;
1133	}
1134
1135	if (AUTH_split == n->norm->An.auth) {
1136		p->flags &= ~TERMP_NOSPLIT;
1137		p->flags |= TERMP_SPLIT;
1138	} else if (AUTH_nosplit == n->norm->An.auth) {
1139		p->flags &= ~TERMP_SPLIT;
1140		p->flags |= TERMP_NOSPLIT;
1141	}
1142
1143}
1144
1145
1146/* ARGSUSED */
1147static int
1148termp_ns_pre(DECL_ARGS)
1149{
1150
1151	if ( ! (MDOC_LINE & n->flags))
1152		p->flags |= TERMP_NOSPACE;
1153	return(1);
1154}
1155
1156
1157/* ARGSUSED */
1158static int
1159termp_rs_pre(DECL_ARGS)
1160{
1161
1162	if (SEC_SEE_ALSO != n->sec)
1163		return(1);
1164	if (MDOC_BLOCK == n->type && n->prev)
1165		term_vspace(p);
1166	return(1);
1167}
1168
1169
1170/* ARGSUSED */
1171static int
1172termp_rv_pre(DECL_ARGS)
1173{
1174	int		 nchild;
1175
1176	term_newln(p);
1177	term_word(p, "The");
1178
1179	nchild = n->nchild;
1180	for (n = n->child; n; n = n->next) {
1181		term_fontpush(p, TERMFONT_BOLD);
1182		term_word(p, n->string);
1183		term_fontpop(p);
1184
1185		p->flags |= TERMP_NOSPACE;
1186		term_word(p, "()");
1187
1188		if (nchild > 2 && n->next) {
1189			p->flags |= TERMP_NOSPACE;
1190			term_word(p, ",");
1191		}
1192
1193		if (n->next && NULL == n->next->next)
1194			term_word(p, "and");
1195	}
1196
1197	if (nchild > 1)
1198		term_word(p, "functions return");
1199	else
1200		term_word(p, "function returns");
1201
1202       	term_word(p, "the value 0 if successful; otherwise the value "
1203			"-1 is returned and the global variable");
1204
1205	term_fontpush(p, TERMFONT_UNDER);
1206	term_word(p, "errno");
1207	term_fontpop(p);
1208
1209       	term_word(p, "is set to indicate the error.");
1210	p->flags |= TERMP_SENTENCE;
1211
1212	return(0);
1213}
1214
1215
1216/* ARGSUSED */
1217static int
1218termp_ex_pre(DECL_ARGS)
1219{
1220	int		 nchild;
1221
1222	term_newln(p);
1223	term_word(p, "The");
1224
1225	nchild = n->nchild;
1226	for (n = n->child; n; n = n->next) {
1227		term_fontpush(p, TERMFONT_BOLD);
1228		term_word(p, n->string);
1229		term_fontpop(p);
1230
1231		if (nchild > 2 && n->next) {
1232			p->flags |= TERMP_NOSPACE;
1233			term_word(p, ",");
1234		}
1235
1236		if (n->next && NULL == n->next->next)
1237			term_word(p, "and");
1238	}
1239
1240	if (nchild > 1)
1241		term_word(p, "utilities exit");
1242	else
1243		term_word(p, "utility exits");
1244
1245       	term_word(p, "0 on success, and >0 if an error occurs.");
1246
1247	p->flags |= TERMP_SENTENCE;
1248	return(0);
1249}
1250
1251
1252/* ARGSUSED */
1253static int
1254termp_nd_pre(DECL_ARGS)
1255{
1256
1257	if (MDOC_BODY != n->type)
1258		return(1);
1259
1260#if defined(__OpenBSD__) || defined(__linux__)
1261	term_word(p, "\\(en");
1262#else
1263	term_word(p, "\\(em");
1264#endif
1265	return(1);
1266}
1267
1268
1269/* ARGSUSED */
1270static int
1271termp_bl_pre(DECL_ARGS)
1272{
1273
1274	return(MDOC_HEAD != n->type);
1275}
1276
1277
1278/* ARGSUSED */
1279static void
1280termp_bl_post(DECL_ARGS)
1281{
1282
1283	if (MDOC_BLOCK == n->type)
1284		term_newln(p);
1285}
1286
1287/* ARGSUSED */
1288static int
1289termp_xr_pre(DECL_ARGS)
1290{
1291
1292	if (NULL == (n = n->child))
1293		return(0);
1294
1295	assert(MDOC_TEXT == n->type);
1296	term_word(p, n->string);
1297
1298	if (NULL == (n = n->next))
1299		return(0);
1300
1301	p->flags |= TERMP_NOSPACE;
1302	term_word(p, "(");
1303	p->flags |= TERMP_NOSPACE;
1304
1305	assert(MDOC_TEXT == n->type);
1306	term_word(p, n->string);
1307
1308	p->flags |= TERMP_NOSPACE;
1309	term_word(p, ")");
1310
1311	return(0);
1312}
1313
1314/*
1315 * This decides how to assert whitespace before any of the SYNOPSIS set
1316 * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain
1317 * macro combos).
1318 */
1319static void
1320synopsis_pre(struct termp *p, const struct mdoc_node *n)
1321{
1322	/*
1323	 * Obviously, if we're not in a SYNOPSIS or no prior macros
1324	 * exist, do nothing.
1325	 */
1326	if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
1327		return;
1328
1329	/*
1330	 * If we're the second in a pair of like elements, emit our
1331	 * newline and return.  UNLESS we're `Fo', `Fn', `Fn', in which
1332	 * case we soldier on.
1333	 */
1334	if (n->prev->tok == n->tok &&
1335			MDOC_Ft != n->tok &&
1336			MDOC_Fo != n->tok &&
1337			MDOC_Fn != n->tok) {
1338		term_newln(p);
1339		return;
1340	}
1341
1342	/*
1343	 * If we're one of the SYNOPSIS set and non-like pair-wise after
1344	 * another (or Fn/Fo, which we've let slip through) then assert
1345	 * vertical space, else only newline and move on.
1346	 */
1347	switch (n->prev->tok) {
1348	case (MDOC_Fd):
1349		/* FALLTHROUGH */
1350	case (MDOC_Fn):
1351		/* FALLTHROUGH */
1352	case (MDOC_Fo):
1353		/* FALLTHROUGH */
1354	case (MDOC_In):
1355		/* FALLTHROUGH */
1356	case (MDOC_Vt):
1357		term_vspace(p);
1358		break;
1359	case (MDOC_Ft):
1360		if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
1361			term_vspace(p);
1362			break;
1363		}
1364		/* FALLTHROUGH */
1365	default:
1366		term_newln(p);
1367		break;
1368	}
1369}
1370
1371
1372static int
1373termp_vt_pre(DECL_ARGS)
1374{
1375
1376	if (MDOC_ELEM == n->type) {
1377		synopsis_pre(p, n);
1378		return(termp_under_pre(p, pair, m, n));
1379	} else if (MDOC_BLOCK == n->type) {
1380		synopsis_pre(p, n);
1381		return(1);
1382	} else if (MDOC_HEAD == n->type)
1383		return(0);
1384
1385	return(termp_under_pre(p, pair, m, n));
1386}
1387
1388
1389/* ARGSUSED */
1390static int
1391termp_bold_pre(DECL_ARGS)
1392{
1393
1394	term_fontpush(p, TERMFONT_BOLD);
1395	return(1);
1396}
1397
1398
1399/* ARGSUSED */
1400static int
1401termp_fd_pre(DECL_ARGS)
1402{
1403
1404	synopsis_pre(p, n);
1405	return(termp_bold_pre(p, pair, m, n));
1406}
1407
1408
1409/* ARGSUSED */
1410static int
1411termp_sh_pre(DECL_ARGS)
1412{
1413
1414	/* No vspace between consecutive `Sh' calls. */
1415
1416	switch (n->type) {
1417	case (MDOC_BLOCK):
1418		if (n->prev && MDOC_Sh == n->prev->tok)
1419			if (NULL == n->prev->body->child)
1420				break;
1421		term_vspace(p);
1422		break;
1423	case (MDOC_HEAD):
1424		term_fontpush(p, TERMFONT_BOLD);
1425		break;
1426	case (MDOC_BODY):
1427		p->offset = term_len(p, p->defindent);
1428		break;
1429	default:
1430		break;
1431	}
1432	return(1);
1433}
1434
1435
1436/* ARGSUSED */
1437static void
1438termp_sh_post(DECL_ARGS)
1439{
1440
1441	switch (n->type) {
1442	case (MDOC_HEAD):
1443		term_newln(p);
1444		break;
1445	case (MDOC_BODY):
1446		term_newln(p);
1447		p->offset = 0;
1448		break;
1449	default:
1450		break;
1451	}
1452}
1453
1454
1455/* ARGSUSED */
1456static int
1457termp_bt_pre(DECL_ARGS)
1458{
1459
1460	term_word(p, "is currently in beta test.");
1461	p->flags |= TERMP_SENTENCE;
1462	return(0);
1463}
1464
1465
1466/* ARGSUSED */
1467static void
1468termp_lb_post(DECL_ARGS)
1469{
1470
1471	if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags)
1472		term_newln(p);
1473}
1474
1475
1476/* ARGSUSED */
1477static int
1478termp_ud_pre(DECL_ARGS)
1479{
1480
1481	term_word(p, "currently under development.");
1482	p->flags |= TERMP_SENTENCE;
1483	return(0);
1484}
1485
1486
1487/* ARGSUSED */
1488static int
1489termp_d1_pre(DECL_ARGS)
1490{
1491
1492	if (MDOC_BLOCK != n->type)
1493		return(1);
1494	term_newln(p);
1495	p->offset += term_len(p, p->defindent + 1);
1496	return(1);
1497}
1498
1499
1500/* ARGSUSED */
1501static void
1502termp_d1_post(DECL_ARGS)
1503{
1504
1505	if (MDOC_BLOCK != n->type)
1506		return;
1507	term_newln(p);
1508}
1509
1510
1511/* ARGSUSED */
1512static int
1513termp_ft_pre(DECL_ARGS)
1514{
1515
1516	/* NB: MDOC_LINE does not effect this! */
1517	synopsis_pre(p, n);
1518	term_fontpush(p, TERMFONT_UNDER);
1519	return(1);
1520}
1521
1522
1523/* ARGSUSED */
1524static int
1525termp_fn_pre(DECL_ARGS)
1526{
1527	int		 pretty;
1528
1529	pretty = MDOC_SYNPRETTY & n->flags;
1530
1531	synopsis_pre(p, n);
1532
1533	if (NULL == (n = n->child))
1534		return(0);
1535
1536	assert(MDOC_TEXT == n->type);
1537	term_fontpush(p, TERMFONT_BOLD);
1538	term_word(p, n->string);
1539	term_fontpop(p);
1540
1541	p->flags |= TERMP_NOSPACE;
1542	term_word(p, "(");
1543	p->flags |= TERMP_NOSPACE;
1544
1545	for (n = n->next; n; n = n->next) {
1546		assert(MDOC_TEXT == n->type);
1547		term_fontpush(p, TERMFONT_UNDER);
1548		term_word(p, n->string);
1549		term_fontpop(p);
1550
1551		if (n->next) {
1552			p->flags |= TERMP_NOSPACE;
1553			term_word(p, ",");
1554		}
1555	}
1556
1557	p->flags |= TERMP_NOSPACE;
1558	term_word(p, ")");
1559
1560	if (pretty) {
1561		p->flags |= TERMP_NOSPACE;
1562		term_word(p, ";");
1563	}
1564
1565	return(0);
1566}
1567
1568
1569/* ARGSUSED */
1570static int
1571termp_fa_pre(DECL_ARGS)
1572{
1573	const struct mdoc_node	*nn;
1574
1575	if (n->parent->tok != MDOC_Fo) {
1576		term_fontpush(p, TERMFONT_UNDER);
1577		return(1);
1578	}
1579
1580	for (nn = n->child; nn; nn = nn->next) {
1581		term_fontpush(p, TERMFONT_UNDER);
1582		term_word(p, nn->string);
1583		term_fontpop(p);
1584
1585		if (nn->next) {
1586			p->flags |= TERMP_NOSPACE;
1587			term_word(p, ",");
1588		}
1589	}
1590
1591	if (n->child && n->next && n->next->tok == MDOC_Fa) {
1592		p->flags |= TERMP_NOSPACE;
1593		term_word(p, ",");
1594	}
1595
1596	return(0);
1597}
1598
1599
1600/* ARGSUSED */
1601static int
1602termp_bd_pre(DECL_ARGS)
1603{
1604	size_t			 tabwidth, rm, rmax;
1605	const struct mdoc_node	*nn;
1606
1607	if (MDOC_BLOCK == n->type) {
1608		print_bvspace(p, n, n);
1609		return(1);
1610	} else if (MDOC_HEAD == n->type)
1611		return(0);
1612
1613	if (n->norm->Bd.offs)
1614		p->offset += a2offs(p, n->norm->Bd.offs);
1615
1616	/*
1617	 * If -ragged or -filled are specified, the block does nothing
1618	 * but change the indentation.  If -unfilled or -literal are
1619	 * specified, text is printed exactly as entered in the display:
1620	 * for macro lines, a newline is appended to the line.  Blank
1621	 * lines are allowed.
1622	 */
1623
1624	if (DISP_literal != n->norm->Bd.type &&
1625			DISP_unfilled != n->norm->Bd.type)
1626		return(1);
1627
1628	tabwidth = p->tabwidth;
1629	if (DISP_literal == n->norm->Bd.type)
1630		p->tabwidth = term_len(p, 8);
1631
1632	rm = p->rmargin;
1633	rmax = p->maxrmargin;
1634	p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
1635
1636	for (nn = n->child; nn; nn = nn->next) {
1637		print_mdoc_node(p, pair, m, nn);
1638		/*
1639		 * If the printed node flushes its own line, then we
1640		 * needn't do it here as well.  This is hacky, but the
1641		 * notion of selective eoln whitespace is pretty dumb
1642		 * anyway, so don't sweat it.
1643		 */
1644		switch (nn->tok) {
1645		case (MDOC_Sm):
1646			/* FALLTHROUGH */
1647		case (MDOC_br):
1648			/* FALLTHROUGH */
1649		case (MDOC_sp):
1650			/* FALLTHROUGH */
1651		case (MDOC_Bl):
1652			/* FALLTHROUGH */
1653		case (MDOC_D1):
1654			/* FALLTHROUGH */
1655		case (MDOC_Dl):
1656			/* FALLTHROUGH */
1657		case (MDOC_Lp):
1658			/* FALLTHROUGH */
1659		case (MDOC_Pp):
1660			continue;
1661		default:
1662			break;
1663		}
1664		if (nn->next && nn->next->line == nn->line)
1665			continue;
1666		term_flushln(p);
1667		p->flags |= TERMP_NOSPACE;
1668	}
1669
1670	p->tabwidth = tabwidth;
1671	p->rmargin = rm;
1672	p->maxrmargin = rmax;
1673	return(0);
1674}
1675
1676
1677/* ARGSUSED */
1678static void
1679termp_bd_post(DECL_ARGS)
1680{
1681	size_t		 rm, rmax;
1682
1683	if (MDOC_BODY != n->type)
1684		return;
1685
1686	rm = p->rmargin;
1687	rmax = p->maxrmargin;
1688
1689	if (DISP_literal == n->norm->Bd.type ||
1690			DISP_unfilled == n->norm->Bd.type)
1691		p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
1692
1693	p->flags |= TERMP_NOSPACE;
1694	term_newln(p);
1695
1696	p->rmargin = rm;
1697	p->maxrmargin = rmax;
1698}
1699
1700
1701/* ARGSUSED */
1702static int
1703termp_bx_pre(DECL_ARGS)
1704{
1705
1706	if (NULL != (n = n->child)) {
1707		term_word(p, n->string);
1708		p->flags |= TERMP_NOSPACE;
1709		term_word(p, "BSD");
1710	} else {
1711		term_word(p, "BSD");
1712		return(0);
1713	}
1714
1715	if (NULL != (n = n->next)) {
1716		p->flags |= TERMP_NOSPACE;
1717		term_word(p, "-");
1718		p->flags |= TERMP_NOSPACE;
1719		term_word(p, n->string);
1720	}
1721
1722	return(0);
1723}
1724
1725
1726/* ARGSUSED */
1727static int
1728termp_xx_pre(DECL_ARGS)
1729{
1730	const char	*pp;
1731	int		 flags;
1732
1733	pp = NULL;
1734	switch (n->tok) {
1735	case (MDOC_Bsx):
1736		pp = "BSD/OS";
1737		break;
1738	case (MDOC_Dx):
1739		pp = "DragonFly";
1740		break;
1741	case (MDOC_Fx):
1742		pp = "FreeBSD";
1743		break;
1744	case (MDOC_Nx):
1745		pp = "NetBSD";
1746		break;
1747	case (MDOC_Ox):
1748		pp = "OpenBSD";
1749		break;
1750	case (MDOC_Ux):
1751		pp = "UNIX";
1752		break;
1753	default:
1754		break;
1755	}
1756
1757	term_word(p, pp);
1758	if (n->child) {
1759		flags = p->flags;
1760		p->flags |= TERMP_KEEP;
1761		term_word(p, n->child->string);
1762		p->flags = flags;
1763	}
1764	return(0);
1765}
1766
1767
1768/* ARGSUSED */
1769static int
1770termp_igndelim_pre(DECL_ARGS)
1771{
1772
1773	p->flags |= TERMP_IGNDELIM;
1774	return(1);
1775}
1776
1777
1778/* ARGSUSED */
1779static void
1780termp_pf_post(DECL_ARGS)
1781{
1782
1783	p->flags |= TERMP_NOSPACE;
1784}
1785
1786
1787/* ARGSUSED */
1788static int
1789termp_ss_pre(DECL_ARGS)
1790{
1791
1792	switch (n->type) {
1793	case (MDOC_BLOCK):
1794		term_newln(p);
1795		if (n->prev)
1796			term_vspace(p);
1797		break;
1798	case (MDOC_HEAD):
1799		term_fontpush(p, TERMFONT_BOLD);
1800		p->offset = term_len(p, (p->defindent+1)/2);
1801		break;
1802	default:
1803		break;
1804	}
1805
1806	return(1);
1807}
1808
1809
1810/* ARGSUSED */
1811static void
1812termp_ss_post(DECL_ARGS)
1813{
1814
1815	if (MDOC_HEAD == n->type)
1816		term_newln(p);
1817}
1818
1819
1820/* ARGSUSED */
1821static int
1822termp_cd_pre(DECL_ARGS)
1823{
1824
1825	synopsis_pre(p, n);
1826	term_fontpush(p, TERMFONT_BOLD);
1827	return(1);
1828}
1829
1830
1831/* ARGSUSED */
1832static int
1833termp_in_pre(DECL_ARGS)
1834{
1835
1836	synopsis_pre(p, n);
1837
1838	if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) {
1839		term_fontpush(p, TERMFONT_BOLD);
1840		term_word(p, "#include");
1841		term_word(p, "<");
1842	} else {
1843		term_word(p, "<");
1844		term_fontpush(p, TERMFONT_UNDER);
1845	}
1846
1847	p->flags |= TERMP_NOSPACE;
1848	return(1);
1849}
1850
1851
1852/* ARGSUSED */
1853static void
1854termp_in_post(DECL_ARGS)
1855{
1856
1857	if (MDOC_SYNPRETTY & n->flags)
1858		term_fontpush(p, TERMFONT_BOLD);
1859
1860	p->flags |= TERMP_NOSPACE;
1861	term_word(p, ">");
1862
1863	if (MDOC_SYNPRETTY & n->flags)
1864		term_fontpop(p);
1865}
1866
1867
1868/* ARGSUSED */
1869static int
1870termp_sp_pre(DECL_ARGS)
1871{
1872	size_t		 i, len;
1873
1874	switch (n->tok) {
1875	case (MDOC_sp):
1876		len = n->child ? a2height(p, n->child->string) : 1;
1877		break;
1878	case (MDOC_br):
1879		len = 0;
1880		break;
1881	default:
1882		len = 1;
1883		break;
1884	}
1885
1886	if (0 == len)
1887		term_newln(p);
1888	for (i = 0; i < len; i++)
1889		term_vspace(p);
1890
1891	return(0);
1892}
1893
1894
1895/* ARGSUSED */
1896static int
1897termp_quote_pre(DECL_ARGS)
1898{
1899
1900	if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
1901		return(1);
1902
1903	switch (n->tok) {
1904	case (MDOC_Ao):
1905		/* FALLTHROUGH */
1906	case (MDOC_Aq):
1907		term_word(p, "<");
1908		break;
1909	case (MDOC_Bro):
1910		/* FALLTHROUGH */
1911	case (MDOC_Brq):
1912		term_word(p, "{");
1913		break;
1914	case (MDOC_Oo):
1915		/* FALLTHROUGH */
1916	case (MDOC_Op):
1917		/* FALLTHROUGH */
1918	case (MDOC_Bo):
1919		/* FALLTHROUGH */
1920	case (MDOC_Bq):
1921		term_word(p, "[");
1922		break;
1923	case (MDOC_Do):
1924		/* FALLTHROUGH */
1925	case (MDOC_Dq):
1926		term_word(p, "``");
1927		break;
1928	case (MDOC_Eo):
1929		break;
1930	case (MDOC_Po):
1931		/* FALLTHROUGH */
1932	case (MDOC_Pq):
1933		term_word(p, "(");
1934		break;
1935	case (MDOC__T):
1936		/* FALLTHROUGH */
1937	case (MDOC_Qo):
1938		/* FALLTHROUGH */
1939	case (MDOC_Qq):
1940		term_word(p, "\"");
1941		break;
1942	case (MDOC_Ql):
1943		/* FALLTHROUGH */
1944	case (MDOC_So):
1945		/* FALLTHROUGH */
1946	case (MDOC_Sq):
1947		term_word(p, "`");
1948		break;
1949	default:
1950		abort();
1951		/* NOTREACHED */
1952	}
1953
1954	p->flags |= TERMP_NOSPACE;
1955	return(1);
1956}
1957
1958
1959/* ARGSUSED */
1960static void
1961termp_quote_post(DECL_ARGS)
1962{
1963
1964	if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
1965		return;
1966
1967	p->flags |= TERMP_NOSPACE;
1968
1969	switch (n->tok) {
1970	case (MDOC_Ao):
1971		/* FALLTHROUGH */
1972	case (MDOC_Aq):
1973		term_word(p, ">");
1974		break;
1975	case (MDOC_Bro):
1976		/* FALLTHROUGH */
1977	case (MDOC_Brq):
1978		term_word(p, "}");
1979		break;
1980	case (MDOC_Oo):
1981		/* FALLTHROUGH */
1982	case (MDOC_Op):
1983		/* FALLTHROUGH */
1984	case (MDOC_Bo):
1985		/* FALLTHROUGH */
1986	case (MDOC_Bq):
1987		term_word(p, "]");
1988		break;
1989	case (MDOC_Do):
1990		/* FALLTHROUGH */
1991	case (MDOC_Dq):
1992		term_word(p, "''");
1993		break;
1994	case (MDOC_Eo):
1995		break;
1996	case (MDOC_Po):
1997		/* FALLTHROUGH */
1998	case (MDOC_Pq):
1999		term_word(p, ")");
2000		break;
2001	case (MDOC__T):
2002		/* FALLTHROUGH */
2003	case (MDOC_Qo):
2004		/* FALLTHROUGH */
2005	case (MDOC_Qq):
2006		term_word(p, "\"");
2007		break;
2008	case (MDOC_Ql):
2009		/* FALLTHROUGH */
2010	case (MDOC_So):
2011		/* FALLTHROUGH */
2012	case (MDOC_Sq):
2013		term_word(p, "'");
2014		break;
2015	default:
2016		abort();
2017		/* NOTREACHED */
2018	}
2019}
2020
2021
2022/* ARGSUSED */
2023static int
2024termp_fo_pre(DECL_ARGS)
2025{
2026
2027	if (MDOC_BLOCK == n->type) {
2028		synopsis_pre(p, n);
2029		return(1);
2030	} else if (MDOC_BODY == n->type) {
2031		p->flags |= TERMP_NOSPACE;
2032		term_word(p, "(");
2033		p->flags |= TERMP_NOSPACE;
2034		return(1);
2035	}
2036
2037	if (NULL == n->child)
2038		return(0);
2039
2040	/* XXX: we drop non-initial arguments as per groff. */
2041
2042	assert(n->child->string);
2043	term_fontpush(p, TERMFONT_BOLD);
2044	term_word(p, n->child->string);
2045	return(0);
2046}
2047
2048
2049/* ARGSUSED */
2050static void
2051termp_fo_post(DECL_ARGS)
2052{
2053
2054	if (MDOC_BODY != n->type)
2055		return;
2056
2057	p->flags |= TERMP_NOSPACE;
2058	term_word(p, ")");
2059
2060	if (MDOC_SYNPRETTY & n->flags) {
2061		p->flags |= TERMP_NOSPACE;
2062		term_word(p, ";");
2063	}
2064}
2065
2066
2067/* ARGSUSED */
2068static int
2069termp_bf_pre(DECL_ARGS)
2070{
2071
2072	if (MDOC_HEAD == n->type)
2073		return(0);
2074	else if (MDOC_BLOCK != n->type)
2075		return(1);
2076
2077	if (FONT_Em == n->norm->Bf.font)
2078		term_fontpush(p, TERMFONT_UNDER);
2079	else if (FONT_Sy == n->norm->Bf.font)
2080		term_fontpush(p, TERMFONT_BOLD);
2081	else
2082		term_fontpush(p, TERMFONT_NONE);
2083
2084	return(1);
2085}
2086
2087
2088/* ARGSUSED */
2089static int
2090termp_sm_pre(DECL_ARGS)
2091{
2092
2093	assert(n->child && MDOC_TEXT == n->child->type);
2094	if (0 == strcmp("on", n->child->string)) {
2095		if (p->col)
2096			p->flags &= ~TERMP_NOSPACE;
2097		p->flags &= ~TERMP_NONOSPACE;
2098	} else
2099		p->flags |= TERMP_NONOSPACE;
2100
2101	return(0);
2102}
2103
2104
2105/* ARGSUSED */
2106static int
2107termp_ap_pre(DECL_ARGS)
2108{
2109
2110	p->flags |= TERMP_NOSPACE;
2111	term_word(p, "'");
2112	p->flags |= TERMP_NOSPACE;
2113	return(1);
2114}
2115
2116
2117/* ARGSUSED */
2118static void
2119termp____post(DECL_ARGS)
2120{
2121
2122	/*
2123	 * Handle lists of authors.  In general, print each followed by
2124	 * a comma.  Don't print the comma if there are only two
2125	 * authors.
2126	 */
2127	if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
2128		if (NULL == n->next->next || MDOC__A != n->next->next->tok)
2129			if (NULL == n->prev || MDOC__A != n->prev->tok)
2130				return;
2131
2132	/* TODO: %U. */
2133
2134	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
2135		return;
2136
2137	p->flags |= TERMP_NOSPACE;
2138	if (NULL == n->next) {
2139		term_word(p, ".");
2140		p->flags |= TERMP_SENTENCE;
2141	} else
2142		term_word(p, ",");
2143}
2144
2145
2146/* ARGSUSED */
2147static int
2148termp_li_pre(DECL_ARGS)
2149{
2150
2151	term_fontpush(p, TERMFONT_NONE);
2152	return(1);
2153}
2154
2155
2156/* ARGSUSED */
2157static int
2158termp_lk_pre(DECL_ARGS)
2159{
2160	const struct mdoc_node *nn, *sv;
2161
2162	term_fontpush(p, TERMFONT_UNDER);
2163
2164	nn = sv = n->child;
2165
2166	if (NULL == nn || NULL == nn->next)
2167		return(1);
2168
2169	for (nn = nn->next; nn; nn = nn->next)
2170		term_word(p, nn->string);
2171
2172	term_fontpop(p);
2173
2174	p->flags |= TERMP_NOSPACE;
2175	term_word(p, ":");
2176
2177	term_fontpush(p, TERMFONT_BOLD);
2178	term_word(p, sv->string);
2179	term_fontpop(p);
2180
2181	return(0);
2182}
2183
2184
2185/* ARGSUSED */
2186static int
2187termp_bk_pre(DECL_ARGS)
2188{
2189
2190	switch (n->type) {
2191	case (MDOC_BLOCK):
2192		break;
2193	case (MDOC_HEAD):
2194		return(0);
2195	case (MDOC_BODY):
2196		if (n->parent->args || 0 == n->prev->nchild)
2197			p->flags |= TERMP_PREKEEP;
2198		break;
2199	default:
2200		abort();
2201		/* NOTREACHED */
2202	}
2203
2204	return(1);
2205}
2206
2207
2208/* ARGSUSED */
2209static void
2210termp_bk_post(DECL_ARGS)
2211{
2212
2213	if (MDOC_BODY == n->type)
2214		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
2215}
2216
2217/* ARGSUSED */
2218static void
2219termp__t_post(DECL_ARGS)
2220{
2221
2222	/*
2223	 * If we're in an `Rs' and there's a journal present, then quote
2224	 * us instead of underlining us (for disambiguation).
2225	 */
2226	if (n->parent && MDOC_Rs == n->parent->tok &&
2227			n->parent->norm->Rs.quote_T)
2228		termp_quote_post(p, pair, m, n);
2229
2230	termp____post(p, pair, m, n);
2231}
2232
2233/* ARGSUSED */
2234static int
2235termp__t_pre(DECL_ARGS)
2236{
2237
2238	/*
2239	 * If we're in an `Rs' and there's a journal present, then quote
2240	 * us instead of underlining us (for disambiguation).
2241	 */
2242	if (n->parent && MDOC_Rs == n->parent->tok &&
2243			n->parent->norm->Rs.quote_T)
2244		return(termp_quote_pre(p, pair, m, n));
2245
2246	term_fontpush(p, TERMFONT_UNDER);
2247	return(1);
2248}
2249
2250/* ARGSUSED */
2251static int
2252termp_under_pre(DECL_ARGS)
2253{
2254
2255	term_fontpush(p, TERMFONT_UNDER);
2256	return(1);
2257}
2258