Home | History | Annotate | Line # | Download | only in dist
      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 (at) openbsd.org>
      4  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps (at) bsd.lv>
      5  * Copyright (c) 2013 Franco Fichtner <franco (at) 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 
     42 struct	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 
     52 struct	mdoc_term_act {
     53 	int	(*pre)(DECL_ARGS);
     54 	void	(*post)(DECL_ARGS);
     55 };
     56 
     57 static	int	  a2width(const struct termp *, const char *);
     58 
     59 static	void	  print_bvspace(struct termp *,
     60 			struct roff_node *, struct roff_node *);
     61 static	void	  print_mdoc_node(DECL_ARGS);
     62 static	void	  print_mdoc_nodelist(DECL_ARGS);
     63 static	void	  print_mdoc_head(struct termp *, const struct roff_meta *);
     64 static	void	  print_mdoc_foot(struct termp *, const struct roff_meta *);
     65 static	void	  synopsis_pre(struct termp *, struct roff_node *);
     66 
     67 static	void	  termp____post(DECL_ARGS);
     68 static	void	  termp__t_post(DECL_ARGS);
     69 static	void	  termp_bd_post(DECL_ARGS);
     70 static	void	  termp_bk_post(DECL_ARGS);
     71 static	void	  termp_bl_post(DECL_ARGS);
     72 static	void	  termp_eo_post(DECL_ARGS);
     73 static	void	  termp_fd_post(DECL_ARGS);
     74 static	void	  termp_fo_post(DECL_ARGS);
     75 static	void	  termp_in_post(DECL_ARGS);
     76 static	void	  termp_it_post(DECL_ARGS);
     77 static	void	  termp_lb_post(DECL_ARGS);
     78 static	void	  termp_nm_post(DECL_ARGS);
     79 static	void	  termp_pf_post(DECL_ARGS);
     80 static	void	  termp_quote_post(DECL_ARGS);
     81 static	void	  termp_sh_post(DECL_ARGS);
     82 static	void	  termp_ss_post(DECL_ARGS);
     83 static	void	  termp_xx_post(DECL_ARGS);
     84 
     85 static	int	  termp__a_pre(DECL_ARGS);
     86 static	int	  termp__t_pre(DECL_ARGS);
     87 static	int	  termp_abort_pre(DECL_ARGS);
     88 static	int	  termp_an_pre(DECL_ARGS);
     89 static	int	  termp_ap_pre(DECL_ARGS);
     90 static	int	  termp_bd_pre(DECL_ARGS);
     91 static	int	  termp_bf_pre(DECL_ARGS);
     92 static	int	  termp_bk_pre(DECL_ARGS);
     93 static	int	  termp_bl_pre(DECL_ARGS);
     94 static	int	  termp_bold_pre(DECL_ARGS);
     95 static	int	  termp_d1_pre(DECL_ARGS);
     96 static	int	  termp_eo_pre(DECL_ARGS);
     97 static	int	  termp_ex_pre(DECL_ARGS);
     98 static	int	  termp_fa_pre(DECL_ARGS);
     99 static	int	  termp_fd_pre(DECL_ARGS);
    100 static	int	  termp_fl_pre(DECL_ARGS);
    101 static	int	  termp_fn_pre(DECL_ARGS);
    102 static	int	  termp_fo_pre(DECL_ARGS);
    103 static	int	  termp_ft_pre(DECL_ARGS);
    104 static	int	  termp_in_pre(DECL_ARGS);
    105 static	int	  termp_it_pre(DECL_ARGS);
    106 static	int	  termp_li_pre(DECL_ARGS);
    107 static	int	  termp_lk_pre(DECL_ARGS);
    108 static	int	  termp_nd_pre(DECL_ARGS);
    109 static	int	  termp_nm_pre(DECL_ARGS);
    110 static	int	  termp_ns_pre(DECL_ARGS);
    111 static	int	  termp_quote_pre(DECL_ARGS);
    112 static	int	  termp_rs_pre(DECL_ARGS);
    113 static	int	  termp_sh_pre(DECL_ARGS);
    114 static	int	  termp_skip_pre(DECL_ARGS);
    115 static	int	  termp_sm_pre(DECL_ARGS);
    116 static	int	  termp_pp_pre(DECL_ARGS);
    117 static	int	  termp_ss_pre(DECL_ARGS);
    118 static	int	  termp_under_pre(DECL_ARGS);
    119 static	int	  termp_vt_pre(DECL_ARGS);
    120 static	int	  termp_xr_pre(DECL_ARGS);
    121 static	int	  termp_xx_pre(DECL_ARGS);
    122 
    123 static 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 
    248 void
    249 terminal_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 
    296 static void
    297 print_mdoc_nodelist(DECL_ARGS)
    298 {
    299 	while (n != NULL) {
    300 		print_mdoc_node(p, pair, meta, n);
    301 		n = n->next;
    302 	}
    303 }
    304 
    305 static void
    306 print_mdoc_node(DECL_ARGS)
    307 {
    308 	const struct mdoc_term_act *act = NULL; // XXX: gcc
    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 
    443 static void
    444 print_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 
    492 static void
    493 print_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 
    561 static int
    562 a2width(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  */
    580 static void
    581 print_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 
    622 static int
    623 termp_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 
    923 static void
    924 termp_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 
    959 static int
    960 termp_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 
   1011 static void
   1012 termp_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 
   1034 static int
   1035 termp_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 
   1051 static int
   1052 termp__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 
   1063 static int
   1064 termp_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 
   1087 static int
   1088 termp_ns_pre(DECL_ARGS)
   1089 {
   1090 
   1091 	if ( ! (NODE_LINE & n->flags))
   1092 		p->flags |= TERMP_NOSPACE;
   1093 	return 1;
   1094 }
   1095 
   1096 static int
   1097 termp_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 
   1106 static int
   1107 termp_ex_pre(DECL_ARGS)
   1108 {
   1109 	term_newln(p);
   1110 	return 1;
   1111 }
   1112 
   1113 static int
   1114 termp_nd_pre(DECL_ARGS)
   1115 {
   1116 	if (n->type == ROFFT_BODY)
   1117 		term_word(p, "\\(en");
   1118 	return 1;
   1119 }
   1120 
   1121 static int
   1122 termp_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 
   1135 static void
   1136 termp_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 
   1148 static int
   1149 termp_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  */
   1178 static void
   1179 synopsis_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 
   1225 static int
   1226 termp_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 
   1241 static int
   1242 termp_bold_pre(DECL_ARGS)
   1243 {
   1244 	term_fontpush(p, TERMFONT_BOLD);
   1245 	return 1;
   1246 }
   1247 
   1248 static int
   1249 termp_fd_pre(DECL_ARGS)
   1250 {
   1251 	synopsis_pre(p, n);
   1252 	return termp_bold_pre(p, pair, meta, n);
   1253 }
   1254 
   1255 static void
   1256 termp_fd_post(DECL_ARGS)
   1257 {
   1258 	term_newln(p);
   1259 }
   1260 
   1261 static int
   1262 termp_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 
   1293 static void
   1294 termp_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 
   1309 static void
   1310 termp_lb_post(DECL_ARGS)
   1311 {
   1312 	if (n->sec == SEC_LIBRARY && n->flags & NODE_LINE)
   1313 		term_newln(p);
   1314 }
   1315 
   1316 static int
   1317 termp_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 
   1329 static int
   1330 termp_ft_pre(DECL_ARGS)
   1331 {
   1332 	synopsis_pre(p, n);
   1333 	return termp_under_pre(p, pair, meta, n);
   1334 }
   1335 
   1336 static int
   1337 termp_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 
   1395 static int
   1396 termp_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 
   1422 static int
   1423 termp_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 
   1465 static void
   1466 termp_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 
   1480 static int
   1481 termp_xx_pre(DECL_ARGS)
   1482 {
   1483 	if ((n->aux = p->flags & TERMP_PREKEEP) == 0)
   1484 		p->flags |= TERMP_PREKEEP;
   1485 	return 1;
   1486 }
   1487 
   1488 static void
   1489 termp_xx_post(DECL_ARGS)
   1490 {
   1491 	if (n->aux == 0)
   1492 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
   1493 }
   1494 
   1495 static void
   1496 termp_pf_post(DECL_ARGS)
   1497 {
   1498 	if (n->next != NULL && (n->next->flags & NODE_LINE) == 0)
   1499 		p->flags |= TERMP_NOSPACE;
   1500 }
   1501 
   1502 static int
   1503 termp_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 
   1527 static void
   1528 termp_ss_post(DECL_ARGS)
   1529 {
   1530 	if (n->type == ROFFT_HEAD || n->type == ROFFT_BODY)
   1531 		term_newln(p);
   1532 }
   1533 
   1534 static int
   1535 termp_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 
   1550 static void
   1551 termp_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 
   1561 static int
   1562 termp_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 
   1570 static int
   1571 termp_skip_pre(DECL_ARGS)
   1572 {
   1573 	return 0;
   1574 }
   1575 
   1576 static int
   1577 termp_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 
   1631 static void
   1632 termp_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 
   1688 static int
   1689 termp_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 
   1708 static void
   1709 termp_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 
   1732 static int
   1733 termp_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 
   1765 static void
   1766 termp_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 
   1781 static int
   1782 termp_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 
   1802 static int
   1803 termp_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 
   1818 static int
   1819 termp_ap_pre(DECL_ARGS)
   1820 {
   1821 	p->flags |= TERMP_NOSPACE;
   1822 	term_word(p, "'");
   1823 	p->flags |= TERMP_NOSPACE;
   1824 	return 1;
   1825 }
   1826 
   1827 static void
   1828 termp____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 
   1856 static int
   1857 termp_li_pre(DECL_ARGS)
   1858 {
   1859 	term_fontpush(p, TERMFONT_NONE);
   1860 	return 1;
   1861 }
   1862 
   1863 static int
   1864 termp_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 
   1905 static int
   1906 termp_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 
   1923 static void
   1924 termp_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  */
   1934 static void
   1935 termp__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 
   1943 static int
   1944 termp__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 
   1953 static int
   1954 termp_under_pre(DECL_ARGS)
   1955 {
   1956 	term_fontpush(p, TERMFONT_UNDER);
   1957 	return 1;
   1958 }
   1959 
   1960 static int
   1961 termp_abort_pre(DECL_ARGS)
   1962 {
   1963 	abort();
   1964 }
   1965