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