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