GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/mandoc/man_term.c Lines: 461 499 92.4 %
Date: 2017-11-13 Branches: 266 325 81.8 %

Line Branch Exec Source
1
/*	$OpenBSD: man_term.c,v 1.162 2017/07/31 15:18:59 schwarze Exp $ */
2
/*
3
 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4
 * Copyright (c) 2010-2015, 2017 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 AUTHORS DISCLAIM ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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
#include <sys/types.h>
19
20
#include <assert.h>
21
#include <ctype.h>
22
#include <limits.h>
23
#include <stdio.h>
24
#include <stdlib.h>
25
#include <string.h>
26
27
#include "mandoc_aux.h"
28
#include "mandoc.h"
29
#include "roff.h"
30
#include "man.h"
31
#include "out.h"
32
#include "term.h"
33
#include "main.h"
34
35
#define	MAXMARGINS	  64 /* maximum number of indented scopes */
36
37
struct	mtermp {
38
	int		  fl;
39
#define	MANT_LITERAL	 (1 << 0)
40
	int		  lmargin[MAXMARGINS]; /* margins (incl. vis. page) */
41
	int		  lmargincur; /* index of current margin */
42
	int		  lmarginsz; /* actual number of nested margins */
43
	size_t		  offset; /* default offset to visible page */
44
	int		  pardist; /* vert. space before par., unit: [v] */
45
};
46
47
#define	DECL_ARGS	  struct termp *p, \
48
			  struct mtermp *mt, \
49
			  struct roff_node *n, \
50
			  const struct roff_meta *meta
51
52
struct	termact {
53
	int		(*pre)(DECL_ARGS);
54
	void		(*post)(DECL_ARGS);
55
	int		  flags;
56
#define	MAN_NOTEXT	 (1 << 0) /* Never has text children. */
57
};
58
59
static	void		  print_man_nodelist(DECL_ARGS);
60
static	void		  print_man_node(DECL_ARGS);
61
static	void		  print_man_head(struct termp *,
62
				const struct roff_meta *);
63
static	void		  print_man_foot(struct termp *,
64
				const struct roff_meta *);
65
static	void		  print_bvspace(struct termp *,
66
				const struct roff_node *, int);
67
68
static	int		  pre_B(DECL_ARGS);
69
static	int		  pre_DT(DECL_ARGS);
70
static	int		  pre_HP(DECL_ARGS);
71
static	int		  pre_I(DECL_ARGS);
72
static	int		  pre_IP(DECL_ARGS);
73
static	int		  pre_OP(DECL_ARGS);
74
static	int		  pre_PD(DECL_ARGS);
75
static	int		  pre_PP(DECL_ARGS);
76
static	int		  pre_RS(DECL_ARGS);
77
static	int		  pre_SH(DECL_ARGS);
78
static	int		  pre_SS(DECL_ARGS);
79
static	int		  pre_TP(DECL_ARGS);
80
static	int		  pre_UR(DECL_ARGS);
81
static	int		  pre_alternate(DECL_ARGS);
82
static	int		  pre_ign(DECL_ARGS);
83
static	int		  pre_in(DECL_ARGS);
84
static	int		  pre_literal(DECL_ARGS);
85
86
static	void		  post_IP(DECL_ARGS);
87
static	void		  post_HP(DECL_ARGS);
88
static	void		  post_RS(DECL_ARGS);
89
static	void		  post_SH(DECL_ARGS);
90
static	void		  post_SS(DECL_ARGS);
91
static	void		  post_TP(DECL_ARGS);
92
static	void		  post_UR(DECL_ARGS);
93
94
static	const struct termact __termacts[MAN_MAX - MAN_TH] = {
95
	{ NULL, NULL, 0 }, /* TH */
96
	{ pre_SH, post_SH, 0 }, /* SH */
97
	{ pre_SS, post_SS, 0 }, /* SS */
98
	{ pre_TP, post_TP, 0 }, /* TP */
99
	{ pre_PP, NULL, 0 }, /* LP */
100
	{ pre_PP, NULL, 0 }, /* PP */
101
	{ pre_PP, NULL, 0 }, /* P */
102
	{ pre_IP, post_IP, 0 }, /* IP */
103
	{ pre_HP, post_HP, 0 }, /* HP */
104
	{ NULL, NULL, 0 }, /* SM */
105
	{ pre_B, NULL, 0 }, /* SB */
106
	{ pre_alternate, NULL, 0 }, /* BI */
107
	{ pre_alternate, NULL, 0 }, /* IB */
108
	{ pre_alternate, NULL, 0 }, /* BR */
109
	{ pre_alternate, NULL, 0 }, /* RB */
110
	{ NULL, NULL, 0 }, /* R */
111
	{ pre_B, NULL, 0 }, /* B */
112
	{ pre_I, NULL, 0 }, /* I */
113
	{ pre_alternate, NULL, 0 }, /* IR */
114
	{ pre_alternate, NULL, 0 }, /* RI */
115
	{ pre_literal, NULL, 0 }, /* nf */
116
	{ pre_literal, NULL, 0 }, /* fi */
117
	{ NULL, NULL, 0 }, /* RE */
118
	{ pre_RS, post_RS, 0 }, /* RS */
119
	{ pre_DT, NULL, 0 }, /* DT */
120
	{ pre_ign, NULL, MAN_NOTEXT }, /* UC */
121
	{ pre_PD, NULL, MAN_NOTEXT }, /* PD */
122
	{ pre_ign, NULL, 0 }, /* AT */
123
	{ pre_in, NULL, MAN_NOTEXT }, /* in */
124
	{ pre_OP, NULL, 0 }, /* OP */
125
	{ pre_literal, NULL, 0 }, /* EX */
126
	{ pre_literal, NULL, 0 }, /* EE */
127
	{ pre_UR, post_UR, 0 }, /* UR */
128
	{ NULL, NULL, 0 }, /* UE */
129
	{ pre_UR, post_UR, 0 }, /* MT */
130
	{ NULL, NULL, 0 }, /* ME */
131
};
132
static	const struct termact *termacts = __termacts - MAN_TH;
133
134
135
void
136
terminal_man(void *arg, const struct roff_man *man)
137
{
138
	struct termp		*p;
139
	struct roff_node	*n;
140
2318
	struct mtermp		 mt;
141
	size_t			 save_defindent;
142
143
1159
	p = (struct termp *)arg;
144
1159
	save_defindent = p->defindent;
145

2318
	if (p->synopsisonly == 0 && p->defindent == 0)
146
526
		p->defindent = 7;
147
1159
	p->tcol->rmargin = p->maxrmargin = p->defrmargin;
148
1159
	term_tab_set(p, NULL);
149
1159
	term_tab_set(p, "T");
150
1159
	term_tab_set(p, ".5i");
151
152
1159
	memset(&mt, 0, sizeof(struct mtermp));
153
1159
	mt.lmargin[mt.lmargincur] = term_len(p, p->defindent);
154
1159
	mt.offset = term_len(p, p->defindent);
155
1159
	mt.pardist = 1;
156
157
1159
	n = man->first->child;
158
1159
	if (p->synopsisonly) {
159
		while (n != NULL) {
160
			if (n->tok == MAN_SH &&
161
			    n->child->child->type == ROFFT_TEXT &&
162
			    !strcmp(n->child->child->string, "SYNOPSIS")) {
163
				if (n->child->next->child != NULL)
164
					print_man_nodelist(p, &mt,
165
					    n->child->next->child,
166
					    &man->meta);
167
				term_newln(p);
168
				break;
169
			}
170
			n = n->next;
171
		}
172
	} else {
173
1159
		term_begin(p, print_man_head, print_man_foot, &man->meta);
174
1159
		p->flags |= TERMP_NOSPACE;
175
1159
		if (n != NULL)
176
1153
			print_man_nodelist(p, &mt, n, &man->meta);
177
1159
		term_end(p);
178
	}
179
1159
	p->defindent = save_defindent;
180
1159
}
181
182
/*
183
 * Printing leading vertical space before a block.
184
 * This is used for the paragraph macros.
185
 * The rules are pretty simple, since there's very little nesting going
186
 * on here.  Basically, if we're the first within another block (SS/SH),
187
 * then don't emit vertical space.  If we are (RS), then do.  If not the
188
 * first, print it.
189
 */
190
static void
191
print_bvspace(struct termp *p, const struct roff_node *n, int pardist)
192
{
193
	int	 i;
194
195
5022
	term_newln(p);
196
197

5022
	if (n->body && n->body->child)
198
2478
		if (n->body->child->type == ROFFT_TBL)
199
			return;
200
201

5010
	if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS)
202
2370
		if (NULL == n->prev)
203
126
			return;
204
205
8784
	for (i = 0; i < pardist; i++)
206
2007
		term_vspace(p);
207
4896
}
208
209
210
static int
211
pre_ign(DECL_ARGS)
212
{
213
214
	return 0;
215
}
216
217
static int
218
pre_I(DECL_ARGS)
219
{
220
221
	term_fontrepl(p, TERMFONT_UNDER);
222
	return 1;
223
}
224
225
static int
226
pre_literal(DECL_ARGS)
227
{
228
229
834
	term_newln(p);
230
231

636
	if (n->tok == MAN_nf || n->tok == MAN_EX)
232
219
		mt->fl |= MANT_LITERAL;
233
	else
234
198
		mt->fl &= ~MANT_LITERAL;
235
236
	/*
237
	 * Unlike .IP and .TP, .HP does not have a HEAD.
238
	 * So in case a second call to term_flushln() is needed,
239
	 * indentation has to be set up explicitly.
240
	 */
241

423
	if (n->parent->tok == MAN_HP && p->tcol->rmargin < p->maxrmargin) {
242
6
		p->tcol->offset = p->tcol->rmargin;
243
6
		p->tcol->rmargin = p->maxrmargin;
244
6
		p->trailspace = 0;
245
6
		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
246
6
		p->flags |= TERMP_NOSPACE;
247
6
	}
248
249
417
	return 0;
250
}
251
252
static int
253
pre_PD(DECL_ARGS)
254
{
255
684
	struct roffsu	 su;
256
257
342
	n = n->child;
258
342
	if (n == NULL) {
259
126
		mt->pardist = 1;
260
126
		return 0;
261
	}
262
216
	assert(n->type == ROFFT_TEXT);
263
216
	if (a2roffsu(n->string, &su, SCALE_VS) != NULL)
264
213
		mt->pardist = term_vspan(p, &su);
265
216
	return 0;
266
342
}
267
268
static int
269
pre_alternate(DECL_ARGS)
270
{
271
330
	enum termfont		 font[2];
272
	struct roff_node	*nn;
273
	int			 savelit, i;
274
275

220
	switch (n->tok) {
276
	case MAN_RB:
277
		font[0] = TERMFONT_NONE;
278
		font[1] = TERMFONT_BOLD;
279
		break;
280
	case MAN_RI:
281
		font[0] = TERMFONT_NONE;
282
		font[1] = TERMFONT_UNDER;
283
		break;
284
	case MAN_BR:
285
		font[0] = TERMFONT_BOLD;
286
		font[1] = TERMFONT_NONE;
287
		break;
288
	case MAN_BI:
289
		font[0] = TERMFONT_BOLD;
290
		font[1] = TERMFONT_UNDER;
291
32
		break;
292
	case MAN_IR:
293
		font[0] = TERMFONT_UNDER;
294
		font[1] = TERMFONT_NONE;
295
		break;
296
	case MAN_IB:
297
		font[0] = TERMFONT_UNDER;
298
		font[1] = TERMFONT_BOLD;
299
78
		break;
300
	default:
301
		abort();
302
	}
303
304
110
	savelit = MANT_LITERAL & mt->fl;
305
110
	mt->fl &= ~MANT_LITERAL;
306
307
736
	for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) {
308
258
		term_fontrepl(p, font[i]);
309

306
		if (savelit && NULL == nn->next)
310
12
			mt->fl |= MANT_LITERAL;
311
258
		assert(nn->type == ROFFT_TEXT);
312
258
		term_word(p, nn->string);
313
258
		if (nn->flags & NODE_EOS)
314
                	p->flags |= TERMP_SENTENCE;
315
258
		if (nn->next)
316
148
			p->flags |= TERMP_NOSPACE;
317
	}
318
319
110
	return 0;
320
110
}
321
322
static int
323
pre_B(DECL_ARGS)
324
{
325
326
162
	term_fontrepl(p, TERMFONT_BOLD);
327
81
	return 1;
328
}
329
330
static int
331
pre_OP(DECL_ARGS)
332
{
333
334
24
	term_word(p, "[");
335
12
	p->flags |= TERMP_NOSPACE;
336
337
12
	if (NULL != (n = n->child)) {
338
9
		term_fontrepl(p, TERMFONT_BOLD);
339
9
		term_word(p, n->string);
340
9
	}
341

21
	if (NULL != n && NULL != n->next) {
342
6
		term_fontrepl(p, TERMFONT_UNDER);
343
6
		term_word(p, n->next->string);
344
6
	}
345
346
12
	term_fontrepl(p, TERMFONT_NONE);
347
12
	p->flags |= TERMP_NOSPACE;
348
12
	term_word(p, "]");
349
12
	return 0;
350
}
351
352
static int
353
pre_in(DECL_ARGS)
354
{
355
78
	struct roffsu	 su;
356
	const char	*cp;
357
	size_t		 v;
358
	int		 less;
359
360
39
	term_newln(p);
361
362
39
	if (n->child == NULL) {
363
		p->tcol->offset = mt->offset;
364
		return 0;
365
	}
366
367
39
	cp = n->child->string;
368
	less = 0;
369
370
39
	if ('-' == *cp)
371
3
		less = -1;
372
36
	else if ('+' == *cp)
373
3
		less = 1;
374
	else
375
33
		cp--;
376
377
39
	if (a2roffsu(++cp, &su, SCALE_EN) == NULL)
378
		return 0;
379
380
39
	v = term_hen(p, &su);
381
382
39
	if (less < 0)
383
9
		p->tcol->offset -= p->tcol->offset > v ? v : p->tcol->offset;
384
36
	else if (less > 0)
385
3
		p->tcol->offset += v;
386
	else
387
		p->tcol->offset = v;
388
39
	if (p->tcol->offset > SHRT_MAX)
389
		p->tcol->offset = term_len(p, p->defindent);
390
391
39
	return 0;
392
39
}
393
394
static int
395
pre_DT(DECL_ARGS)
396
{
397
	term_tab_set(p, NULL);
398
	term_tab_set(p, "T");
399
	term_tab_set(p, ".5i");
400
	return 0;
401
}
402
403
static int
404
pre_HP(DECL_ARGS)
405
{
406
1926
	struct roffsu		 su;
407
	const struct roff_node	*nn;
408
	int			 len;
409
410
963
	switch (n->type) {
411
	case ROFFT_BLOCK:
412
321
		print_bvspace(p, n, mt->pardist);
413
321
		return 1;
414
	case ROFFT_BODY:
415
		break;
416
	default:
417
321
		return 0;
418
	}
419
420
321
	if ( ! (MANT_LITERAL & mt->fl)) {
421
309
		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
422
309
		p->trailspace = 2;
423
309
	}
424
425
	/* Calculate offset. */
426
427

612
	if ((nn = n->parent->head->child) != NULL &&
428
291
	    a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
429
291
		len = term_hen(p, &su);
430

321
		if (len < 0 && (size_t)(-len) > mt->offset)
431
3
			len = -mt->offset;
432
288
		else if (len > SHRT_MAX)
433
			len = term_len(p, p->defindent);
434
291
		mt->lmargin[mt->lmargincur] = len;
435
291
	} else
436
30
		len = mt->lmargin[mt->lmargincur];
437
438
321
	p->tcol->offset = mt->offset;
439
321
	p->tcol->rmargin = mt->offset + len;
440
321
	return 1;
441
963
}
442
443
static void
444
post_HP(DECL_ARGS)
445
{
446
447
1926
	switch (n->type) {
448
	case ROFFT_BODY:
449
321
		term_newln(p);
450
451
		/*
452
		 * Compatibility with a groff bug.
453
		 * The .HP macro uses the undocumented .tag request
454
		 * which causes a line break and cancels no-space
455
		 * mode even if there isn't any output.
456
		 */
457
458
321
		if (n->child == NULL)
459
3
			term_vspace(p);
460
461
321
		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
462
321
		p->trailspace = 0;
463
321
		p->tcol->offset = mt->offset;
464
321
		p->tcol->rmargin = p->maxrmargin;
465
321
		break;
466
	default:
467
		break;
468
	}
469
963
}
470
471
static int
472
pre_PP(DECL_ARGS)
473
{
474
475
8100
	switch (n->type) {
476
	case ROFFT_BLOCK:
477
1350
		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
478
1350
		print_bvspace(p, n, mt->pardist);
479
1350
		break;
480
	default:
481
2700
		p->tcol->offset = mt->offset;
482
2700
		break;
483
	}
484
485
4050
	return n->type != ROFFT_HEAD;
486
}
487
488
static int
489
pre_IP(DECL_ARGS)
490
{
491
1344
	struct roffsu		 su;
492
	const struct roff_node	*nn;
493
	int			 len, savelit;
494
495

768
	switch (n->type) {
496
	case ROFFT_BODY:
497
192
		p->flags |= TERMP_NOSPACE;
498
192
		break;
499
	case ROFFT_HEAD:
500
192
		p->flags |= TERMP_NOBREAK;
501
192
		p->trailspace = 1;
502
192
		break;
503
	case ROFFT_BLOCK:
504
192
		print_bvspace(p, n, mt->pardist);
505
		/* FALLTHROUGH */
506
	default:
507
192
		return 1;
508
	}
509
510
	/* Calculate the offset from the optional second argument. */
511

630
	if ((nn = n->parent->head->child) != NULL &&
512
378
	    (nn = nn->next) != NULL &&
513
246
	    a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
514
234
		len = term_hen(p, &su);
515

258
		if (len < 0 && (size_t)(-len) > mt->offset)
516
12
			len = -mt->offset;
517
222
		else if (len > SHRT_MAX)
518
			len = term_len(p, p->defindent);
519
234
		mt->lmargin[mt->lmargincur] = len;
520
234
	} else
521
150
		len = mt->lmargin[mt->lmargincur];
522
523
576
	switch (n->type) {
524
	case ROFFT_HEAD:
525
192
		p->tcol->offset = mt->offset;
526
192
		p->tcol->rmargin = mt->offset + len;
527
528
192
		savelit = MANT_LITERAL & mt->fl;
529
192
		mt->fl &= ~MANT_LITERAL;
530
531
192
		if (n->child)
532
189
			print_man_node(p, mt, n->child, meta);
533
534
192
		if (savelit)
535
45
			mt->fl |= MANT_LITERAL;
536
537
192
		return 0;
538
	case ROFFT_BODY:
539
192
		p->tcol->offset = mt->offset + len;
540
192
		p->tcol->rmargin = p->maxrmargin;
541
192
		break;
542
	default:
543
		break;
544
	}
545
546
192
	return 1;
547
576
}
548
549
static void
550
post_IP(DECL_ARGS)
551
{
552
553
1536
	switch (n->type) {
554
	case ROFFT_HEAD:
555
192
		term_flushln(p);
556
192
		p->flags &= ~TERMP_NOBREAK;
557
192
		p->trailspace = 0;
558
192
		p->tcol->rmargin = p->maxrmargin;
559
192
		break;
560
	case ROFFT_BODY:
561
192
		term_newln(p);
562
192
		p->tcol->offset = mt->offset;
563
192
		break;
564
	default:
565
		break;
566
	}
567
576
}
568
569
static int
570
pre_TP(DECL_ARGS)
571
{
572
4536
	struct roffsu		 su;
573
	struct roff_node	*nn;
574
	int			 len, savelit;
575
576

2592
	switch (n->type) {
577
	case ROFFT_HEAD:
578
648
		p->flags |= TERMP_NOBREAK | TERMP_BRTRSP;
579
648
		p->trailspace = 1;
580
648
		break;
581
	case ROFFT_BODY:
582
648
		p->flags |= TERMP_NOSPACE;
583
648
		break;
584
	case ROFFT_BLOCK:
585
648
		print_bvspace(p, n, mt->pardist);
586
		/* FALLTHROUGH */
587
	default:
588
648
		return 1;
589
	}
590
591
	/* Calculate offset. */
592
593

2430
	if ((nn = n->parent->head->child) != NULL &&
594

2580
	    nn->string != NULL && ! (NODE_LINE & nn->flags) &&
595
1134
	    a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
596
1116
		len = term_hen(p, &su);
597

1140
		if (len < 0 && (size_t)(-len) > mt->offset)
598
12
			len = -mt->offset;
599
1104
		else if (len > SHRT_MAX)
600
			len = term_len(p, p->defindent);
601
1116
		mt->lmargin[mt->lmargincur] = len;
602
1116
	} else
603
180
		len = mt->lmargin[mt->lmargincur];
604
605
1944
	switch (n->type) {
606
	case ROFFT_HEAD:
607
648
		p->tcol->offset = mt->offset;
608
648
		p->tcol->rmargin = mt->offset + len;
609
610
648
		savelit = MANT_LITERAL & mt->fl;
611
648
		mt->fl &= ~MANT_LITERAL;
612
613
		/* Don't print same-line elements. */
614
648
		nn = n->child;
615

3699
		while (NULL != nn && 0 == (NODE_LINE & nn->flags))
616
585
			nn = nn->next;
617
618
2610
		while (NULL != nn) {
619
657
			print_man_node(p, mt, nn, meta);
620
657
			nn = nn->next;
621
		}
622
623
648
		if (savelit)
624
39
			mt->fl |= MANT_LITERAL;
625
648
		return 0;
626
	case ROFFT_BODY:
627
648
		p->tcol->offset = mt->offset + len;
628
648
		p->tcol->rmargin = p->maxrmargin;
629
648
		p->trailspace = 0;
630
648
		p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP);
631
648
		break;
632
	default:
633
		break;
634
	}
635
636
648
	return 1;
637
1944
}
638
639
static void
640
post_TP(DECL_ARGS)
641
{
642
643
5184
	switch (n->type) {
644
	case ROFFT_HEAD:
645
648
		term_flushln(p);
646
648
		break;
647
	case ROFFT_BODY:
648
648
		term_newln(p);
649
648
		p->tcol->offset = mt->offset;
650
648
		break;
651
	default:
652
		break;
653
	}
654
1944
}
655
656
static int
657
pre_SS(DECL_ARGS)
658
{
659
	int	 i;
660
661

1665
	switch (n->type) {
662
	case ROFFT_BLOCK:
663
185
		mt->fl &= ~MANT_LITERAL;
664
185
		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
665
185
		mt->offset = term_len(p, p->defindent);
666
667
		/*
668
		 * No vertical space before the first subsection
669
		 * and after an empty subsection.
670
		 */
671
672
185
		do {
673
185
			n = n->prev;
674

481
		} while (n != NULL && n->tok >= MAN_TH &&
675
132
		    termacts[n->tok].flags & MAN_NOTEXT);
676

457
		if (n == NULL || (n->tok == MAN_SS && n->body->child == NULL))
677
			break;
678
679
656
		for (i = 0; i < mt->pardist; i++)
680
164
			term_vspace(p);
681
		break;
682
	case ROFFT_HEAD:
683
185
		term_fontrepl(p, TERMFONT_BOLD);
684
185
		p->tcol->offset = term_len(p, 3);
685
185
		p->tcol->rmargin = mt->offset;
686
185
		p->trailspace = mt->offset;
687
185
		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
688
185
		break;
689
	case ROFFT_BODY:
690
185
		p->tcol->offset = mt->offset;
691
185
		p->tcol->rmargin = p->maxrmargin;
692
185
		p->trailspace = 0;
693
185
		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
694
185
		break;
695
	default:
696
		break;
697
	}
698
699
555
	return 1;
700
}
701
702
static void
703
post_SS(DECL_ARGS)
704
{
705
706
1480
	switch (n->type) {
707
	case ROFFT_HEAD:
708
185
		term_newln(p);
709
185
		break;
710
	case ROFFT_BODY:
711
185
		term_newln(p);
712
185
		break;
713
	default:
714
		break;
715
	}
716
555
}
717
718
static int
719
pre_SH(DECL_ARGS)
720
{
721
	int	 i;
722
723

22590
	switch (n->type) {
724
	case ROFFT_BLOCK:
725
2510
		mt->fl &= ~MANT_LITERAL;
726
2510
		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
727
2510
		mt->offset = term_len(p, p->defindent);
728
729
		/*
730
		 * No vertical space before the first section
731
		 * and after an empty section.
732
		 */
733
734
2510
		do {
735
2510
			n = n->prev;
736

5230
		} while (n != NULL && n->tok >= MAN_TH &&
737
1357
		    termacts[n->tok].flags & MAN_NOTEXT);
738

5227
		if (n == NULL || (n->tok == MAN_SH && n->body->child == NULL))
739
			break;
740
741
5392
		for (i = 0; i < mt->pardist; i++)
742
1348
			term_vspace(p);
743
		break;
744
	case ROFFT_HEAD:
745
2510
		term_fontrepl(p, TERMFONT_BOLD);
746
2510
		p->tcol->offset = 0;
747
2510
		p->tcol->rmargin = mt->offset;
748
2510
		p->trailspace = mt->offset;
749
2510
		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
750
2510
		break;
751
	case ROFFT_BODY:
752
2510
		p->tcol->offset = mt->offset;
753
2510
		p->tcol->rmargin = p->maxrmargin;
754
2510
		p->trailspace = 0;
755
2510
		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
756
2510
		break;
757
	default:
758
		break;
759
	}
760
761
7530
	return 1;
762
}
763
764
static void
765
post_SH(DECL_ARGS)
766
{
767
768
20080
	switch (n->type) {
769
	case ROFFT_HEAD:
770
2510
		term_newln(p);
771
2510
		break;
772
	case ROFFT_BODY:
773
2510
		term_newln(p);
774
2510
		break;
775
	default:
776
		break;
777
	}
778
7530
}
779
780
static int
781
pre_RS(DECL_ARGS)
782
{
783
2232
	struct roffsu	 su;
784
785
1116
	switch (n->type) {
786
	case ROFFT_BLOCK:
787
372
		term_newln(p);
788
372
		return 1;
789
	case ROFFT_HEAD:
790
372
		return 0;
791
	default:
792
		break;
793
	}
794
795
372
	n = n->parent->head;
796
372
	n->aux = SHRT_MAX + 1;
797
372
	if (n->child == NULL)
798
45
		n->aux = mt->lmargin[mt->lmargincur];
799
327
	else if (a2roffsu(n->child->string, &su, SCALE_EN) != NULL)
800
327
		n->aux = term_hen(p, &su);
801

762
	if (n->aux < 0 && (size_t)(-n->aux) > mt->offset)
802
3
		n->aux = -mt->offset;
803
369
	else if (n->aux > SHRT_MAX)
804
		n->aux = term_len(p, p->defindent);
805
806
375
	mt->offset += n->aux;
807
372
	p->tcol->offset = mt->offset;
808
372
	p->tcol->rmargin = p->maxrmargin;
809
810
372
	if (++mt->lmarginsz < MAXMARGINS)
811
372
		mt->lmargincur = mt->lmarginsz;
812
813
372
	mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
814
372
	return 1;
815
1116
}
816
817
static void
818
post_RS(DECL_ARGS)
819
{
820
821
2232
	switch (n->type) {
822
	case ROFFT_BLOCK:
823
		return;
824
	case ROFFT_HEAD:
825
		return;
826
	default:
827
372
		term_newln(p);
828
		break;
829
	}
830
831
372
	mt->offset -= n->parent->head->aux;
832
372
	p->tcol->offset = mt->offset;
833
834
372
	if (--mt->lmarginsz < MAXMARGINS)
835
372
		mt->lmargincur = mt->lmarginsz;
836
1116
}
837
838
static int
839
pre_UR(DECL_ARGS)
840
{
841
842
252
	return n->type != ROFFT_HEAD;
843
}
844
845
static void
846
post_UR(DECL_ARGS)
847
{
848
849
252
	if (n->type != ROFFT_BLOCK)
850
		return;
851
852
42
	term_word(p, "<");
853
42
	p->flags |= TERMP_NOSPACE;
854
855
42
	if (NULL != n->child->child)
856
36
		print_man_node(p, mt, n->child->child, meta);
857
858
42
	p->flags |= TERMP_NOSPACE;
859
42
	term_word(p, ">");
860
168
}
861
862
static void
863
print_man_node(DECL_ARGS)
864
{
865
	int		 c;
866
867

80122
	switch (n->type) {
868
	case ROFFT_TEXT:
869
		/*
870
		 * If we have a blank line, output a vertical space.
871
		 * If we have a space as the first character, break
872
		 * before printing the line's data.
873
		 */
874
19788
		if (*n->string == '\0') {
875
21
			if (p->flags & TERMP_NONEWLINE)
876
3
				term_newln(p);
877
			else
878
18
				term_vspace(p);
879
21
			return;
880

19797
		} else if (*n->string == ' ' && n->flags & NODE_LINE &&
881
15
		    (p->flags & TERMP_NONEWLINE) == 0)
882
15
			term_newln(p);
883
884
19767
		term_word(p, n->string);
885
19767
		goto out;
886
887
	case ROFFT_EQN:
888
		if ( ! (n->flags & NODE_LINE))
889
			p->flags |= TERMP_NOSPACE;
890
		term_eqn(p, n->eqn);
891
		if (n->next != NULL && ! (n->next->flags & NODE_LINE))
892
			p->flags |= TERMP_NOSPACE;
893
		return;
894
	case ROFFT_TBL:
895
921
		if (p->tbl.cols == NULL)
896
356
			term_vspace(p);
897
921
		term_tbl(p, n->span);
898
921
		return;
899
	default:
900
		break;
901
	}
902
903
19352
	if (n->tok < ROFF_MAX) {
904
1416
		roff_term_pre(p, n);
905
1416
		return;
906
	}
907
908

35872
	assert(n->tok >= MAN_TH && n->tok <= MAN_MAX);
909
17936
	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
910
17555
		term_fontrepl(p, TERMFONT_NONE);
911
912
	c = 1;
913
17936
	if (termacts[n->tok].pre)
914
17861
		c = (*termacts[n->tok].pre)(p, mt, n, meta);
915
916

32027
	if (c && n->child)
917
13995
		print_man_nodelist(p, mt, n->child, meta);
918
919
17936
	if (termacts[n->tok].post)
920
12810
		(*termacts[n->tok].post)(p, mt, n, meta);
921
17936
	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
922
17555
		term_fontrepl(p, TERMFONT_NONE);
923
924
out:
925
	/*
926
	 * If we're in a literal context, make sure that words
927
	 * together on the same line stay together.  This is a
928
	 * POST-printing call, so we check the NEXT word.  Since
929
	 * -man doesn't have nested macros, we don't need to be
930
	 * more specific than this.
931
	 */
932

41846
	if (mt->fl & MANT_LITERAL &&
933
4650
	    ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) &&
934
8739
	    (n->next == NULL || n->next->flags & NODE_LINE)) {
935
4458
		p->flags |= TERMP_BRNEVER | TERMP_NOSPACE;
936

8250
		if (n->string != NULL && *n->string != '\0')
937
3792
			term_flushln(p);
938
		else
939
666
			term_newln(p);
940
4458
		p->flags &= ~TERMP_BRNEVER;
941

4470
		if (p->tcol->rmargin < p->maxrmargin &&
942
12
		    n->parent->tok == MAN_HP) {
943
12
			p->tcol->offset = p->tcol->rmargin;
944
12
			p->tcol->rmargin = p->maxrmargin;
945
12
		}
946
	}
947
37703
	if (NODE_EOS & n->flags)
948
1029
		p->flags |= TERMP_SENTENCE;
949
77764
}
950
951
952
static void
953
print_man_nodelist(DECL_ARGS)
954
{
955
956
123802
	while (n != NULL) {
957
39179
		print_man_node(p, mt, n, meta);
958
39179
		n = n->next;
959
	}
960
15148
}
961
962
static void
963
print_man_foot(struct termp *p, const struct roff_meta *meta)
964
{
965
2318
	char			*title;
966
	size_t			 datelen, titlen;
967
968
1159
	assert(meta->title);
969
1159
	assert(meta->msec);
970
1159
	assert(meta->date);
971
972
1159
	term_fontrepl(p, TERMFONT_NONE);
973
974
1159
	if (meta->hasbody)
975
1153
		term_vspace(p);
976
977
	/*
978
	 * Temporary, undocumented option to imitate mdoc(7) output.
979
	 * In the bottom right corner, use the operating system
980
	 * instead of the title.
981
	 */
982
983
1159
	if ( ! p->mdocstyle) {
984
526
		if (meta->hasbody) {
985
523
			term_vspace(p);
986
523
			term_vspace(p);
987
523
		}
988
526
		mandoc_asprintf(&title, "%s(%s)",
989
526
		    meta->title, meta->msec);
990
1159
	} else if (meta->os) {
991
633
		title = mandoc_strdup(meta->os);
992
633
	} else {
993
		title = mandoc_strdup("");
994
	}
995
1159
	datelen = term_strlen(p, meta->date);
996
997
	/* Bottom left corner: operating system. */
998
999
1159
	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
1000
1159
	p->trailspace = 1;
1001
1159
	p->tcol->offset = 0;
1002
3471
	p->tcol->rmargin = p->maxrmargin > datelen ?
1003
1153
	    (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0;
1004
1005
1159
	if (meta->os)
1006
1159
		term_word(p, meta->os);
1007
1159
	term_flushln(p);
1008
1009
	/* At the bottom in the middle: manual date. */
1010
1011
1159
	p->tcol->offset = p->tcol->rmargin;
1012
1159
	titlen = term_strlen(p, title);
1013
3471
	p->tcol->rmargin = p->maxrmargin > titlen ?
1014
1153
	    p->maxrmargin - titlen : 0;
1015
1159
	p->flags |= TERMP_NOSPACE;
1016
1017
1159
	term_word(p, meta->date);
1018
1159
	term_flushln(p);
1019
1020
	/* Bottom right corner: manual title and section. */
1021
1022
1159
	p->flags &= ~TERMP_NOBREAK;
1023
1159
	p->flags |= TERMP_NOSPACE;
1024
1159
	p->trailspace = 0;
1025
1159
	p->tcol->offset = p->tcol->rmargin;
1026
1159
	p->tcol->rmargin = p->maxrmargin;
1027
1028
1159
	term_word(p, title);
1029
1159
	term_flushln(p);
1030
1159
	free(title);
1031
1159
}
1032
1033
static void
1034
print_man_head(struct termp *p, const struct roff_meta *meta)
1035
{
1036
	const char		*volume;
1037
2318
	char			*title;
1038
	size_t			 vollen, titlen;
1039
1040
1159
	assert(meta->title);
1041
1159
	assert(meta->msec);
1042
1043
3468
	volume = NULL == meta->vol ? "" : meta->vol;
1044
1159
	vollen = term_strlen(p, volume);
1045
1046
	/* Top left corner: manual title and section. */
1047
1048
1159
	mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec);
1049
1159
	titlen = term_strlen(p, title);
1050
1051
1159
	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1052
1159
	p->trailspace = 1;
1053
1159
	p->tcol->offset = 0;
1054
3471
	p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
1055
1153
	    (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
1056
9
	    vollen < p->maxrmargin ? p->maxrmargin - vollen : 0;
1057
1058
1159
	term_word(p, title);
1059
1159
	term_flushln(p);
1060
1061
	/* At the top in the middle: manual volume. */
1062
1063
1159
	p->flags |= TERMP_NOSPACE;
1064
1159
	p->tcol->offset = p->tcol->rmargin;
1065
3477
	p->tcol->rmargin = p->tcol->offset + vollen + titlen <
1066
2318
	    p->maxrmargin ?  p->maxrmargin - titlen : p->maxrmargin;
1067
1068
1159
	term_word(p, volume);
1069
1159
	term_flushln(p);
1070
1071
	/* Top right corner: title and section, again. */
1072
1073
1159
	p->flags &= ~TERMP_NOBREAK;
1074
1159
	p->trailspace = 0;
1075
1159
	if (p->tcol->rmargin + titlen <= p->maxrmargin) {
1076
1153
		p->flags |= TERMP_NOSPACE;
1077
1153
		p->tcol->offset = p->tcol->rmargin;
1078
1153
		p->tcol->rmargin = p->maxrmargin;
1079
1153
		term_word(p, title);
1080
1153
		term_flushln(p);
1081
1153
	}
1082
1083
1159
	p->flags &= ~TERMP_NOSPACE;
1084
1159
	p->tcol->offset = 0;
1085
1159
	p->tcol->rmargin = p->maxrmargin;
1086
1087
	/*
1088
	 * Groff prints three blank lines before the content.
1089
	 * Do the same, except in the temporary, undocumented
1090
	 * mode imitating mdoc(7) output.
1091
	 */
1092
1093
1159
	term_vspace(p);
1094
1159
	if ( ! p->mdocstyle) {
1095
526
		term_vspace(p);
1096
526
		term_vspace(p);
1097
526
	}
1098
1159
	free(title);
1099
1159
}