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