GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/mandoc/term.c Lines: 413 495 83.4 %
Date: 2017-11-13 Branches: 284 372 76.3 %

Line Branch Exec Source
1
/*	$OpenBSD: term.c,v 1.134 2017/07/28 14:24:17 florian Exp $ */
2
/*
3
 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4
 * Copyright (c) 2010-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 <stdio.h>
23
#include <stdlib.h>
24
#include <string.h>
25
26
#include "mandoc.h"
27
#include "mandoc_aux.h"
28
#include "out.h"
29
#include "term.h"
30
#include "main.h"
31
32
static	size_t		 cond_width(const struct termp *, int, int *);
33
static	void		 adjbuf(struct termp_col *, size_t);
34
static	void		 bufferc(struct termp *, char);
35
static	void		 encode(struct termp *, const char *, size_t);
36
static	void		 encode1(struct termp *, int);
37
static	void		 endline(struct termp *);
38
39
40
void
41
term_setcol(struct termp *p, size_t maxtcol)
42
{
43
28668
	if (maxtcol > p->maxtcol) {
44
143
		p->tcols = mandoc_recallocarray(p->tcols,
45
		    p->maxtcol, maxtcol, sizeof(*p->tcols));
46
143
		p->maxtcol = maxtcol;
47
143
	}
48
14334
	p->lasttcol = maxtcol - 1;
49
14334
	p->tcol = p->tcols;
50
14334
}
51
52
void
53
term_free(struct termp *p)
54
{
55
11514
	for (p->tcol = p->tcols; p->tcol < p->tcols + p->maxtcol; p->tcol++)
56
2532
		free(p->tcol->buf);
57
2150
	free(p->tcols);
58
2150
	free(p->fontq);
59
2150
	free(p);
60
2150
}
61
62
void
63
term_begin(struct termp *p, term_margin head,
64
		term_margin foot, const struct roff_meta *arg)
65
{
66
67
4300
	p->headf = head;
68
2150
	p->footf = foot;
69
2150
	p->argf = arg;
70
2150
	(*p->begin)(p);
71
2150
}
72
73
void
74
term_end(struct termp *p)
75
{
76
77
4300
	(*p->end)(p);
78
2150
}
79
80
/*
81
 * Flush a chunk of text.  By default, break the output line each time
82
 * the right margin is reached, and continue output on the next line
83
 * at the same offset as the chunk itself.  By default, also break the
84
 * output line at the end of the chunk.
85
 * The following flags may be specified:
86
 *
87
 *  - TERMP_NOBREAK: Do not break the output line at the right margin,
88
 *    but only at the max right margin.  Also, do not break the output
89
 *    line at the end of the chunk, such that the next call can pad to
90
 *    the next column.  However, if less than p->trailspace blanks,
91
 *    which can be 0, 1, or 2, remain to the right margin, the line
92
 *    will be broken.
93
 *  - TERMP_BRTRSP: Consider trailing whitespace significant
94
 *    when deciding whether the chunk fits or not.
95
 *  - TERMP_BRIND: If the chunk does not fit and the output line has
96
 *    to be broken, start the next line at the right margin instead
97
 *    of at the offset.  Used together with TERMP_NOBREAK for the tags
98
 *    in various kinds of tagged lists.
99
 *  - TERMP_HANG: Do not break the output line at the right margin,
100
 *    append the next chunk after it even if this one is too long.
101
 *    To be used together with TERMP_NOBREAK.
102
 *  - TERMP_NOPAD: Start writing at the current position,
103
 *    do not pad with blank characters up to the offset.
104
 */
105
void
106
term_flushln(struct termp *p)
107
{
108
	size_t		 vis;   /* current visual position on output */
109
	size_t		 vbl;   /* number of blanks to prepend to output */
110
	size_t		 vend;	/* end of word visual position on output */
111
	size_t		 bp;    /* visual right border position */
112
	size_t		 dv;    /* temporary for visual pos calculations */
113
	size_t		 j;     /* temporary loop index for p->tcol->buf */
114
	size_t		 jhy;	/* last hyph before overflow w/r/t j */
115
	size_t		 maxvis; /* output position of visible boundary */
116
	int		 ntab;	/* number of tabs to prepend */
117
	int		 breakline; /* after this word */
118
119

315408
	vbl = (p->flags & TERMP_NOPAD) || p->tcol->offset < p->viscol ?
120
62901
	    0 : p->tcol->offset - p->viscol;
121

73121
	if (p->minbl && vbl < p->minbl)
122
114
		vbl = p->minbl;
123
189270
	maxvis = p->tcol->rmargin > p->viscol + vbl ?
124
62946
	    p->tcol->rmargin - p->viscol - vbl : 0;
125
175803
	bp = !(p->flags & TERMP_NOBREAK) ? maxvis :
126
27360
	    p->maxrmargin > p->viscol + vbl ?
127
13677
	    p->maxrmargin - p->viscol - vbl : 0;
128
	vis = vend = 0;
129
130
63162
	if ((p->flags & TERMP_MULTICOL) == 0)
131
44385
		p->tcol->col = 0;
132
305440
	while (p->tcol->col < p->tcol->lastcol) {
133
134
		/*
135
		 * Handle literal tab characters: collapse all
136
		 * subsequent tabs into a single huge set of spaces.
137
		 */
138
139
		ntab = 0;
140

546741
		while (p->tcol->col < p->tcol->lastcol &&
141
182247
		    p->tcol->buf[p->tcol->col] == '\t') {
142
3098
			vend = term_tab_next(vis);
143
3098
			vbl += vend - vis;
144
			vis = vend;
145
3098
			ntab++;
146
3098
			p->tcol->col++;
147
		}
148
149
		/*
150
		 * Count up visible word characters.  Control sequences
151
		 * (starting with the CSI) aren't counted.  A space
152
		 * generates a non-printing word, which is valid (the
153
		 * space is printed according to regular spacing rules).
154
		 */
155
156
		jhy = 0;
157
		breakline = 0;
158
2414296
		for (j = p->tcol->col; j < p->tcol->lastcol; j++) {
159
1144157
			if (p->tcol->buf[j] == '\n') {
160
12
				if ((p->flags & TERMP_BRIND) == 0)
161
12
					breakline = 1;
162
				continue;
163
			}
164

2175086
			if (p->tcol->buf[j] == ' ' || p->tcol->buf[j] == '\t')
165
				break;
166
167
			/* Back over the last printed character. */
168
1027987
			if (p->tcol->buf[j] == '\b') {
169
97023
				assert(j);
170
97023
				vend -= (*p->width)(p, p->tcol->buf[j - 1]);
171
97023
				continue;
172
			}
173
174
			/* Regular word. */
175
			/* Break at the hyphen point if we overrun. */
176

2392495
			if (vend > vis && vend < bp &&
177
727856
			    (p->tcol->buf[j] == ASCII_HYPH||
178
725920
			     p->tcol->buf[j] == ASCII_BREAK))
179
1954
				jhy = j;
180
181
			/*
182
			 * Hyphenation now decided, put back a real
183
			 * hyphen such that we get the correct width.
184
			 */
185
930964
			if (p->tcol->buf[j] == ASCII_HYPH)
186
1948
				p->tcol->buf[j] = '-';
187
188
930964
			vend += (*p->width)(p, p->tcol->buf[j]);
189
930964
		}
190
191
		/*
192
		 * Find out whether we would exceed the right margin.
193
		 * If so, break to the next line.
194
		 */
195
196

181426
		if (vend > bp && jhy == 0 && vis > 0 &&
197
2277
		    (p->flags & TERMP_BRNEVER) == 0) {
198
2244
			if (p->flags & TERMP_MULTICOL)
199
45
				return;
200
201
2199
			endline(p);
202
2199
			vend -= vis;
203
204
			/* Use pending tabs on the new line. */
205
206
			vbl = 0;
207
4410
			while (ntab--)
208
6
				vbl = term_tab_next(vbl);
209
210
			/* Re-establish indentation. */
211
212
2199
			if (p->flags & TERMP_BRIND)
213
273
				vbl += p->tcol->rmargin;
214
			else
215
1926
				vbl += p->tcol->offset;
216
6267
			maxvis = p->tcol->rmargin > vbl ?
217
1869
			    p->tcol->rmargin - vbl : 0;
218
6315
			bp = !(p->flags & TERMP_NOBREAK) ? maxvis :
219
534
			    p->maxrmargin > vbl ?  p->maxrmargin - vbl : 0;
220
2199
		}
221
222
		/*
223
		 * Write out the rest of the word.
224
		 */
225
226
2413216
		for ( ; p->tcol->col < p->tcol->lastcol; p->tcol->col++) {
227

1144211
			if (vend > bp && jhy > 0 && p->tcol->col > jhy)
228
				break;
229
1143641
			if (p->tcol->buf[p->tcol->col] == '\n')
230
				continue;
231
1143629
			if (p->tcol->buf[p->tcol->col] == '\t')
232
				break;
233
1140675
			if (p->tcol->buf[p->tcol->col] == ' ') {
234
				j = p->tcol->col;
235

681537
				while (p->tcol->col < p->tcol->lastcol &&
236
227131
				    p->tcol->buf[p->tcol->col] == ' ')
237
113996
					p->tcol->col++;
238
113183
				dv = (p->tcol->col - j) * (*p->width)(p, ' ');
239
113183
				vbl += dv;
240
113183
				vend += dv;
241
113183
				break;
242
			}
243
1027492
			if (p->tcol->buf[p->tcol->col] == ASCII_NBRSP) {
244
8121
				vbl += (*p->width)(p, ' ');
245
8121
				continue;
246
			}
247
1019371
			if (p->tcol->buf[p->tcol->col] == ASCII_BREAK)
248
				continue;
249
250
			/*
251
			 * Now we definitely know there will be
252
			 * printable characters to output,
253
			 * so write preceding white space now.
254
			 */
255
1019353
			if (vbl) {
256
165778
				(*p->advance)(p, vbl);
257
165778
				p->viscol += vbl;
258
				vbl = 0;
259
165778
			}
260
261
1019353
			(*p->letter)(p, p->tcol->buf[p->tcol->col]);
262
1019353
			if (p->tcol->buf[p->tcol->col] == '\b')
263
96975
				p->viscol -= (*p->width)(p,
264
96975
				    p->tcol->buf[p->tcol->col - 1]);
265
			else
266
922378
				p->viscol += (*p->width)(p,
267
				    p->tcol->buf[p->tcol->col]);
268
		}
269
		vis = vend;
270
271
179104
		if (breakline == 0)
272
179092
			continue;
273
274
		/* Explicitly requested output line break. */
275
276
12
		if (p->flags & TERMP_MULTICOL)
277
			return;
278
279
12
		endline(p);
280
		breakline = 0;
281
		vis = vend = 0;
282
283
		/* Re-establish indentation. */
284
285
12
		vbl = p->tcol->offset;
286
36
		maxvis = p->tcol->rmargin > vbl ?
287
12
		    p->tcol->rmargin - vbl : 0;
288
36
		bp = !(p->flags & TERMP_NOBREAK) ? maxvis :
289
		    p->maxrmargin > vbl ?  p->maxrmargin - vbl : 0;
290
	}
291
292
	/*
293
	 * If there was trailing white space, it was not printed;
294
	 * so reset the cursor position accordingly.
295
	 */
296
297
63117
	if (vis > vbl)
298
62917
		vis -= vbl;
299
	else
300
		vis = 0;
301
302
63117
	p->col = p->tcol->col = p->tcol->lastcol = 0;
303
63117
	p->minbl = p->trailspace;
304
63117
	p->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE | TERMP_NOPAD);
305
306
63117
	if (p->flags & TERMP_MULTICOL)
307
18732
		return;
308
309
	/* Trailing whitespace is significant in some columns. */
310
311

44463
	if (vis && vbl && (TERMP_BRTRSP & p->flags))
312
33
		vis += vbl;
313
314
	/* If the column was overrun, break the line. */
315

57468
	if ((p->flags & TERMP_NOBREAK) == 0 ||
316
13683
	    ((p->flags & TERMP_HANG) == 0 &&
317
13083
	     vis + p->trailspace * (*p->width)(p, ' ') > maxvis))
318
34027
		endline(p);
319
107547
}
320
321
static void
322
endline(struct termp *p)
323
{
324
72476
	if ((p->flags & (TERMP_NEWMC | TERMP_ENDMC)) == TERMP_ENDMC) {
325
		p->mc = NULL;
326
		p->flags &= ~TERMP_ENDMC;
327
	}
328
36238
	if (p->mc != NULL) {
329
		if (p->viscol && p->maxrmargin >= p->viscol)
330
			(*p->advance)(p, p->maxrmargin - p->viscol + 1);
331
		p->flags |= TERMP_NOBUF | TERMP_NOSPACE;
332
		term_word(p, p->mc);
333
		p->flags &= ~(TERMP_NOBUF | TERMP_NEWMC);
334
	}
335
36238
	p->viscol = 0;
336
36238
	p->minbl = 0;
337
36238
	(*p->endline)(p);
338
36238
}
339
340
/*
341
 * A newline only breaks an existing line; it won't assert vertical
342
 * space.  All data in the output buffer is flushed prior to the newline
343
 * assertion.
344
 */
345
void
346
term_newln(struct termp *p)
347
{
348
349
84906
	p->flags |= TERMP_NOSPACE;
350

65872
	if (p->tcol->lastcol || p->viscol)
351
19106
		term_flushln(p);
352
42453
}
353
354
/*
355
 * Asserts a vertical space (a full, empty line-break between lines).
356
 * Note that if used twice, this will cause two blank spaces and so on.
357
 * All data in the output buffer is flushed prior to the newline
358
 * assertion.
359
 */
360
void
361
term_vspace(struct termp *p)
362
{
363
364
33160
	term_newln(p);
365
16580
	p->viscol = 0;
366
16580
	p->minbl = 0;
367
16580
	if (0 < p->skipvsp)
368
2295
		p->skipvsp--;
369
	else
370
14285
		(*p->endline)(p);
371
16580
}
372
373
/* Swap current and previous font; for \fP and .ft P */
374
void
375
term_fontlast(struct termp *p)
376
{
377
	enum termfont	 f;
378
379
654
	f = p->fontl;
380
327
	p->fontl = p->fontq[p->fonti];
381
327
	p->fontq[p->fonti] = f;
382
327
}
383
384
/* Set font, save current, discard previous; for \f, .ft, .B etc. */
385
void
386
term_fontrepl(struct termp *p, enum termfont f)
387
{
388
389
97622
	p->fontl = p->fontq[p->fonti];
390
48811
	p->fontq[p->fonti] = f;
391
48811
}
392
393
/* Set font, save previous. */
394
void
395
term_fontpush(struct termp *p, enum termfont f)
396
{
397
398
17460
	p->fontl = p->fontq[p->fonti];
399
8730
	if (++p->fonti == p->fontsz) {
400
		p->fontsz += 8;
401
		p->fontq = mandoc_reallocarray(p->fontq,
402
		    p->fontsz, sizeof(*p->fontq));
403
	}
404
8730
	p->fontq[p->fonti] = f;
405
8730
}
406
407
/* Flush to make the saved pointer current again. */
408
void
409
term_fontpopq(struct termp *p, int i)
410
{
411
412
144088
	assert(i >= 0);
413
72044
	if (p->fonti > i)
414
6942
		p->fonti = i;
415
72044
}
416
417
/* Pop one font off the stack. */
418
void
419
term_fontpop(struct termp *p)
420
{
421
422
3576
	assert(p->fonti);
423
1788
	p->fonti--;
424
1788
}
425
426
/*
427
 * Handle pwords, partial words, which may be either a single word or a
428
 * phrase that cannot be broken down (such as a literal string).  This
429
 * handles word styling.
430
 */
431
void
432
term_word(struct termp *p, const char *word)
433
{
434
170516
	struct roffsu	 su;
435
	const char	 nbrsp[2] = { ASCII_NBRSP, 0 };
436
170516
	const char	*seq, *cp;
437
170516
	int		 sz, uc;
438
170516
	size_t		 csz, lsz, ssz;
439
	enum mandoc_esc	 esc;
440
441
170516
	if ((p->flags & TERMP_NOBUF) == 0) {
442
170516
		if ((p->flags & TERMP_NOSPACE) == 0) {
443
17905
			if ((p->flags & TERMP_KEEP) == 0) {
444
16435
				bufferc(p, ' ');
445
16435
				if (p->flags & TERMP_SENTENCE)
446
585
					bufferc(p, ' ');
447
			} else
448
1470
				bufferc(p, ASCII_NBRSP);
449
		}
450
170516
		if (p->flags & TERMP_PREKEEP)
451
2436
			p->flags |= TERMP_KEEP;
452
341032
		if (p->flags & TERMP_NONOSPACE)
453
170516
			p->flags |= TERMP_NOSPACE;
454
		else
455
170516
			p->flags &= ~TERMP_NOSPACE;
456
170516
		p->flags &= ~(TERMP_SENTENCE | TERMP_NONEWLINE);
457
170516
		p->skipvsp = 0;
458
170516
	}
459
460
542014
	while ('\0' != *word) {
461
197611
		if ('\\' != *word) {
462
175279
			if (TERMP_NBRWORD & p->flags) {
463
504
				if (' ' == *word) {
464
165
					encode(p, nbrsp, 1);
465
165
					word++;
466
165
					continue;
467
				}
468
339
				ssz = strcspn(word, "\\ ");
469
339
			} else
470
174775
				ssz = strcspn(word, "\\");
471
175114
			encode(p, word, ssz);
472
175114
			word += (int)ssz;
473
175114
			continue;
474
		}
475
476
22332
		word++;
477
22332
		esc = mandoc_escape(&word, &seq, &sz);
478
22332
		if (ESCAPE_ERROR == esc)
479
9
			continue;
480
481




22323
		switch (esc) {
482
		case ESCAPE_UNICODE:
483
3066
			uc = mchars_num2uc(seq + 1, sz - 1);
484
3066
			break;
485
		case ESCAPE_NUMBERED:
486
317
			uc = mchars_num2char(seq, sz);
487
317
			if (uc < 0)
488
12
				continue;
489
			break;
490
		case ESCAPE_SPECIAL:
491
9988
			if (p->enc == TERMENC_ASCII) {
492
8722
				cp = mchars_spec2str(seq, sz, &ssz);
493
8722
				if (cp != NULL)
494
8642
					encode(p, cp, ssz);
495
			} else {
496
1266
				uc = mchars_spec2cp(seq, sz);
497
1266
				if (uc > 0)
498
1206
					encode1(p, uc);
499
			}
500
9988
			continue;
501
		case ESCAPE_FONTBOLD:
502
2358
			term_fontrepl(p, TERMFONT_BOLD);
503
2358
			continue;
504
		case ESCAPE_FONTITALIC:
505
1641
			term_fontrepl(p, TERMFONT_UNDER);
506
1641
			continue;
507
		case ESCAPE_FONTBI:
508
6
			term_fontrepl(p, TERMFONT_BI);
509
6
			continue;
510
		case ESCAPE_FONT:
511
		case ESCAPE_FONTROMAN:
512
4467
			term_fontrepl(p, TERMFONT_NONE);
513
4467
			continue;
514
		case ESCAPE_FONTPREV:
515
309
			term_fontlast(p);
516
309
			continue;
517
		case ESCAPE_BREAK:
518
12
			bufferc(p, '\n');
519
12
			continue;
520
		case ESCAPE_NOSPACE:
521
21
			if (p->flags & TERMP_BACKAFTER)
522
3
				p->flags &= ~TERMP_BACKAFTER;
523
18
			else if (*word == '\0')
524
18
				p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE);
525
21
			continue;
526
		case ESCAPE_HORIZ:
527
21
			if (*seq == '|') {
528
3
				seq++;
529
3
				uc = -p->col;
530
3
			} else
531
				uc = 0;
532
21
			if (a2roffsu(seq, &su, SCALE_EM) == NULL)
533
				continue;
534
21
			uc += term_hen(p, &su);
535
21
			if (uc > 0)
536
30
				while (uc-- > 0)
537
9
					bufferc(p, ASCII_NBRSP);
538
15
			else if (p->col > (size_t)(-uc))
539
15
				p->col += uc;
540
			else {
541
				uc += p->col;
542
				p->col = 0;
543
				if (p->tcol->offset > (size_t)(-uc)) {
544
					p->ti += uc;
545
					p->tcol->offset += uc;
546
				} else {
547
					p->ti -= p->tcol->offset;
548
					p->tcol->offset = 0;
549
				}
550
			}
551
21
			continue;
552
		case ESCAPE_HLINE:
553
21
			if ((cp = a2roffsu(seq, &su, SCALE_EM)) == NULL)
554
				continue;
555
21
			uc = term_hen(p, &su);
556
21
			if (uc <= 0) {
557
				if (p->tcol->rmargin <= p->tcol->offset)
558
					continue;
559
				lsz = p->tcol->rmargin - p->tcol->offset;
560
			} else
561
21
				lsz = uc;
562
21
			if (*cp == seq[-1])
563
6
				uc = -1;
564
15
			else if (*cp == '\\') {
565
6
				seq = cp + 1;
566
6
				esc = mandoc_escape(&seq, &cp, &sz);
567

6
				switch (esc) {
568
				case ESCAPE_UNICODE:
569
					uc = mchars_num2uc(cp + 1, sz - 1);
570
					break;
571
				case ESCAPE_NUMBERED:
572
					uc = mchars_num2char(cp, sz);
573
					break;
574
				case ESCAPE_SPECIAL:
575
6
					uc = mchars_spec2cp(cp, sz);
576
6
					break;
577
				default:
578
					uc = -1;
579
					break;
580
				}
581
			} else
582
				uc = *cp;
583

36
			if (uc < 0x20 || (uc > 0x7E && uc < 0xA0))
584
6
				uc = '_';
585
21
			if (p->enc == TERMENC_ASCII) {
586
21
				cp = ascii_uc2str(uc);
587
21
				csz = term_strlen(p, cp);
588
21
				ssz = strlen(cp);
589
21
			} else
590
				csz = (*p->width)(p, uc);
591
204
			while (lsz >= csz) {
592
81
				if (p->enc == TERMENC_ASCII)
593
81
					encode(p, cp, ssz);
594
				else
595
					encode1(p, uc);
596
81
				lsz -= csz;
597
			}
598
21
			continue;
599
		case ESCAPE_SKIPCHAR:
600
27
			p->flags |= TERMP_BACKAFTER;
601
27
			continue;
602
		case ESCAPE_OVERSTRIKE:
603
21
			cp = seq + sz;
604
120
			while (seq < cp) {
605
39
				if (*seq == '\\') {
606
					mandoc_escape(&seq, NULL, NULL);
607
					continue;
608
				}
609
39
				encode1(p, *seq++);
610
39
				if (seq < cp) {
611
21
					if (p->flags & TERMP_BACKBEFORE)
612
						p->flags |= TERMP_BACKAFTER;
613
					else
614
						p->flags |= TERMP_BACKBEFORE;
615
21
				}
616
			}
617
			/* Trim trailing backspace/blank pair. */
618

42
			if (p->tcol->lastcol > 2 &&
619
21
			    (p->tcol->buf[p->tcol->lastcol - 1] == ' ' ||
620
21
			     p->tcol->buf[p->tcol->lastcol - 1] == '\t'))
621
				p->tcol->lastcol -= 2;
622
21
			if (p->col > p->tcol->lastcol)
623
				p->col = p->tcol->lastcol;
624
21
			continue;
625
		default:
626
48
			continue;
627
		}
628
629
		/*
630
		 * Common handling for Unicode and numbered
631
		 * character escape sequences.
632
		 */
633
634
3371
		if (p->enc == TERMENC_ASCII) {
635
2084
			cp = ascii_uc2str(uc);
636
2084
			encode(p, cp, strlen(cp));
637
2084
		} else {
638
1287
			if ((uc < 0x20 && uc != 0x09) ||
639
1227
			    (uc > 0x7E && uc < 0xA0))
640
114
				uc = 0xFFFD;
641
1287
			encode1(p, uc);
642
		}
643
	}
644
170516
	p->flags &= ~TERMP_NBRWORD;
645
170516
}
646
647
static void
648
adjbuf(struct termp_col *c, size_t sz)
649
{
650
4880
	if (c->maxcols == 0)
651
2440
		c->maxcols = 1024;
652
4880
	while (c->maxcols <= sz)
653
		c->maxcols <<= 2;
654
2440
	c->buf = mandoc_reallocarray(c->buf, c->maxcols, sizeof(*c->buf));
655
2440
}
656
657
static void
658
bufferc(struct termp *p, char c)
659
{
660
37022
	if (p->flags & TERMP_NOBUF) {
661
		(*p->letter)(p, c);
662
		return;
663
	}
664
18511
	if (p->col + 1 >= p->tcol->maxcols)
665
		adjbuf(p->tcol, p->col + 1);
666

18511
	if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
667
18511
		p->tcol->buf[p->col] = c;
668
18511
	if (p->tcol->lastcol < ++p->col)
669
18511
		p->tcol->lastcol = p->col;
670
18511
}
671
672
/*
673
 * See encode().
674
 * Do this for a single (probably unicode) value.
675
 * Does not check for non-decorated glyphs.
676
 */
677
static void
678
encode1(struct termp *p, int c)
679
{
680
	enum termfont	  f;
681
682
1653154
	if (p->flags & TERMP_NOBUF) {
683
		(*p->letter)(p, c);
684
		return;
685
	}
686
687
826577
	if (p->col + 7 >= p->tcol->maxcols)
688
3
		adjbuf(p->tcol, p->col + 7);
689
690

3302125
	f = (c == ASCII_HYPH || c > 127 || isgraph(c)) ?
691
826571
	    p->fontq[p->fonti] : TERMFONT_NONE;
692
693
826577
	if (p->flags & TERMP_BACKBEFORE) {
694

78
		if (p->tcol->buf[p->col - 1] == ' ' ||
695
39
		    p->tcol->buf[p->col - 1] == '\t')
696
			p->col--;
697
		else
698
39
			p->tcol->buf[p->col++] = '\b';
699
39
		p->flags &= ~TERMP_BACKBEFORE;
700
39
	}
701
826577
	if (f == TERMFONT_UNDER || f == TERMFONT_BI) {
702
23883
		p->tcol->buf[p->col++] = '_';
703
23883
		p->tcol->buf[p->col++] = '\b';
704
23883
	}
705
826577
	if (f == TERMFONT_BOLD || f == TERMFONT_BI) {
706
71924
		if (c == ASCII_HYPH)
707
744
			p->tcol->buf[p->col++] = '-';
708
		else
709
71180
			p->tcol->buf[p->col++] = c;
710
71924
		p->tcol->buf[p->col++] = '\b';
711
71924
	}
712

826577
	if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
713
826577
		p->tcol->buf[p->col] = c;
714
826577
	if (p->tcol->lastcol < ++p->col)
715
826577
		p->tcol->lastcol = p->col;
716
826577
	if (p->flags & TERMP_BACKAFTER) {
717
24
		p->flags |= TERMP_BACKBEFORE;
718
24
		p->flags &= ~TERMP_BACKAFTER;
719
24
	}
720
1653154
}
721
722
static void
723
encode(struct termp *p, const char *word, size_t sz)
724
{
725
	size_t		  i;
726
727
372172
	if (p->flags & TERMP_NOBUF) {
728
		for (i = 0; i < sz; i++)
729
			(*p->letter)(p, word[i]);
730
		return;
731
	}
732
733
186086
	if (p->col + 2 + (sz * 5) >= p->tcol->maxcols)
734
2437
		adjbuf(p->tcol, p->col + 2 + (sz * 5));
735
736
2235976
	for (i = 0; i < sz; i++) {
737

1861856
		if (ASCII_HYPH == word[i] ||
738
929954
		    isgraph((unsigned char)word[i]))
739
824045
			encode1(p, word[i]);
740
		else {
741

107857
			if (p->tcol->lastcol <= p->col ||
742
			    (word[i] != ' ' && word[i] != ASCII_NBRSP))
743
107857
				p->tcol->buf[p->col] = word[i];
744
107857
			p->col++;
745
746
			/*
747
			 * Postpone the effect of \z while handling
748
			 * an overstrike sequence from ascii_uc2str().
749
			 */
750
751

108986
			if (word[i] == '\b' &&
752
1129
			    (p->flags & TERMP_BACKBEFORE)) {
753
				p->flags &= ~TERMP_BACKBEFORE;
754
				p->flags |= TERMP_BACKAFTER;
755
			}
756
		}
757
	}
758
186086
	if (p->tcol->lastcol < p->col)
759
9023
		p->tcol->lastcol = p->col;
760
372172
}
761
762
void
763
term_setwidth(struct termp *p, const char *wstr)
764
{
765
60
	struct roffsu	 su;
766
	int		 iop, width;
767
768
	iop = 0;
769
	width = 0;
770
30
	if (NULL != wstr) {
771
30
		switch (*wstr) {
772
		case '+':
773
			iop = 1;
774
6
			wstr++;
775
6
			break;
776
		case '-':
777
			iop = -1;
778
			wstr++;
779
			break;
780
		default:
781
			break;
782
		}
783
24
		if (a2roffsu(wstr, &su, SCALE_MAX) != NULL)
784
18
			width = term_hspan(p, &su);
785
		else
786
			iop = 0;
787
	}
788
30
	(*p->setwidth)(p, iop, width);
789
30
}
790
791
size_t
792
term_len(const struct termp *p, size_t sz)
793
{
794
795
45084
	return (*p->width)(p, ' ') * sz;
796
}
797
798
static size_t
799
cond_width(const struct termp *p, int c, int *skip)
800
{
801
802
629256
	if (*skip) {
803
		(*skip) = 0;
804
		return 0;
805
	} else
806
314628
		return (*p->width)(p, c);
807
314628
}
808
809
size_t
810
term_strlen(const struct termp *p, const char *cp)
811
{
812
116718
	size_t		 sz, rsz, i;
813
116718
	int		 ssz, skip, uc;
814
116718
	const char	*seq, *rhs;
815
	enum mandoc_esc	 esc;
816
	static const char rej[] = { '\\', ASCII_NBRSP, ASCII_HYPH,
817
			ASCII_BREAK, '\0' };
818
819
	/*
820
	 * Account for escaped sequences within string length
821
	 * calculations.  This follows the logic in term_word() as we
822
	 * must calculate the width of produced strings.
823
	 */
824
825
	sz = 0;
826
116718
	skip = 0;
827
467264
	while ('\0' != *cp) {
828
117081
		rsz = strcspn(cp, rej);
829
795858
		for (i = 0; i < rsz; i++)
830
280848
			sz += cond_width(p, *cp++, &skip);
831
832

150645
		switch (*cp) {
833
		case '\\':
834
694
			cp++;
835
694
			esc = mandoc_escape(&cp, &seq, &ssz);
836
694
			if (ESCAPE_ERROR == esc)
837
				continue;
838
839
			rhs = NULL;
840
841

694
			switch (esc) {
842
			case ESCAPE_UNICODE:
843
576
				uc = mchars_num2uc(seq + 1, ssz - 1);
844
576
				break;
845
			case ESCAPE_NUMBERED:
846
				uc = mchars_num2char(seq, ssz);
847
				if (uc < 0)
848
					continue;
849
				break;
850
			case ESCAPE_SPECIAL:
851
112
				if (p->enc == TERMENC_ASCII) {
852
70
					rhs = mchars_spec2str(seq, ssz, &rsz);
853
70
					if (rhs != NULL)
854
						break;
855
				} else {
856
42
					uc = mchars_spec2cp(seq, ssz);
857
42
					if (uc > 0)
858
						sz += cond_width(p, uc, &skip);
859
				}
860
112
				continue;
861
			case ESCAPE_SKIPCHAR:
862
				skip = 1;
863
				continue;
864
			case ESCAPE_OVERSTRIKE:
865
				rsz = 0;
866
				rhs = seq + ssz;
867
				while (seq < rhs) {
868
					if (*seq == '\\') {
869
						mandoc_escape(&seq, NULL, NULL);
870
						continue;
871
					}
872
					i = (*p->width)(p, *seq++);
873
					if (rsz < i)
874
						rsz = i;
875
				}
876
				sz += rsz;
877
				continue;
878
			default:
879
6
				continue;
880
			}
881
882
			/*
883
			 * Common handling for Unicode and numbered
884
			 * character escape sequences.
885
			 */
886
887
576
			if (rhs == NULL) {
888
576
				if (p->enc == TERMENC_ASCII) {
889
360
					rhs = ascii_uc2str(uc);
890
360
					rsz = strlen(rhs);
891
				} else {
892
216
					if ((uc < 0x20 && uc != 0x09) ||
893
204
					    (uc > 0x7E && uc < 0xA0))
894
30
						uc = 0xFFFD;
895
216
					sz += cond_width(p, uc, &skip);
896
216
					continue;
897
				}
898
360
			}
899
900
360
			if (skip) {
901
				skip = 0;
902
				break;
903
			}
904
905
			/*
906
			 * Common handling for all escape sequences
907
			 * printing more than one character.
908
			 */
909
910
3020
			for (i = 0; i < rsz; i++)
911
1150
				sz += (*p->width)(p, *rhs++);
912
			break;
913
		case ASCII_NBRSP:
914
33564
			sz += cond_width(p, ' ', &skip);
915
33564
			cp++;
916
33564
			break;
917
		case ASCII_HYPH:
918
			sz += cond_width(p, '-', &skip);
919
			cp++;
920
			break;
921
		default:
922
			break;
923
		}
924
	}
925
926
116718
	return sz;
927
116718
}
928
929
int
930
term_vspan(const struct termp *p, const struct roffsu *su)
931
{
932
	double		 r;
933
	int		 ri;
934
935


2178
	switch (su->unit) {
936
	case SCALE_BU:
937
12
		r = su->scale / 40.0;
938
12
		break;
939
	case SCALE_CM:
940
9
		r = su->scale * 6.0 / 2.54;
941
9
		break;
942
	case SCALE_FS:
943
		r = su->scale * 65536.0 / 40.0;
944
		break;
945
	case SCALE_IN:
946
6
		r = su->scale * 6.0;
947
6
		break;
948
	case SCALE_MM:
949
		r = su->scale * 0.006;
950
		break;
951
	case SCALE_PC:
952
12
		r = su->scale;
953
12
		break;
954
	case SCALE_PT:
955
15
		r = su->scale / 12.0;
956
15
		break;
957
	case SCALE_EN:
958
	case SCALE_EM:
959
18
		r = su->scale * 0.6;
960
18
		break;
961
	case SCALE_VS:
962
1017
		r = su->scale;
963
1017
		break;
964
	default:
965
		abort();
966
	}
967
1089
	ri = r > 0.0 ? r + 0.4995 : r - 0.4995;
968
1089
	return ri < 66 ? ri : 1;
969
}
970
971
/*
972
 * Convert a scaling width to basic units, rounding towards 0.
973
 */
974
int
975
term_hspan(const struct termp *p, const struct roffsu *su)
976
{
977
978
36
	return (*p->hspan)(p, su);
979
}
980
981
/*
982
 * Convert a scaling width to basic units, rounding to closest.
983
 */
984
int
985
term_hen(const struct termp *p, const struct roffsu *su)
986
{
987
	int bu;
988
989
23412
	if ((bu = (*p->hspan)(p, su)) >= 0)
990
11445
		return (bu + 11) / 24;
991
	else
992
261
		return -((-bu + 11) / 24);
993
11706
}