GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/mandoc/mdoc_term.c Lines: 0 835 0.0 %
Date: 2016-12-06 Branches: 0 728 0.0 %

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