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