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