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