GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/mandoc/term.c Lines: 403 479 84.1 %
Date: 2017-11-07 Branches: 286 372 76.9 %

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
84996
	if (maxtcol > p->maxtcol) {
44
423
		p->tcols = mandoc_recallocarray(p->tcols,
45
		    p->maxtcol, maxtcol, sizeof(*p->tcols));
46
423
		p->maxtcol = maxtcol;
47
423
	}
48
42498
	p->lasttcol = maxtcol - 1;
49
42498
	p->tcol = p->tcols;
50
42498
}
51
52
void
53
term_free(struct termp *p)
54
{
55
34297
	for (p->tcol = p->tcols; p->tcol < p->tcols + p->maxtcol; p->tcol++)
56
7529
		free(p->tcol->buf);
57
6413
	free(p->tcols);
58
6413
	free(p->fontq);
59
6413
	free(p);
60
6413
}
61
62
void
63
term_begin(struct termp *p, term_margin head,
64
		term_margin foot, const struct roff_meta *arg)
65
{
66
67
12826
	p->headf = head;
68
6413
	p->footf = foot;
69
6413
	p->argf = arg;
70
6413
	(*p->begin)(p);
71
6413
}
72
73
void
74
term_end(struct termp *p)
75
{
76
77
12826
	(*p->end)(p);
78
6413
}
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

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

217138
	if (p->minbl && vbl < p->minbl)
122
342
		vbl = p->minbl;
123
560310
	maxvis = p->tcol->rmargin > p->viscol + vbl ?
124
186338
	    p->tcol->rmargin - p->viscol - vbl : 0;
125
519720
	bp = !(p->flags & TERMP_NOBREAK) ? maxvis :
126
82458
	    p->maxrmargin > p->viscol + vbl ?
127
41220
	    p->maxrmargin - p->viscol - vbl : 0;
128
	vis = vend = 0;
129
130
186986
	if ((p->flags & TERMP_MULTICOL) == 0)
131
131663
		p->tcol->col = 0;
132
720638
	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

1623609
		while (p->tcol->col < p->tcol->lastcol &&
141
541203
		    p->tcol->buf[p->tcol->col] == '\t') {
142
7452
			vend = term_tab_next(vis);
143
7452
			vbl += vend - vis;
144
			vis = vend;
145
7452
			ntab++;
146
7452
			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
7176382
		for (j = p->tcol->col; j < p->tcol->lastcol; j++) {
159
3401718
			if (p->tcol->buf[j] == '\n') {
160
36
				if ((p->flags & TERMP_BRIND) == 0)
161
36
					breakline = 1;
162
				continue;
163
			}
164

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

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

541135
		if (vend > bp && jhy == 0 && vis > 0 &&
197
7384
		    (p->flags & TERMP_BRNEVER) == 0) {
198
7285
			if (p->flags & TERMP_MULTICOL)
199
135
				return;
200
201
7150
			endline(p);
202
7150
			vend -= vis;
203
204
			/* Use pending tabs on the new line. */
205
206
			vbl = 0;
207
14336
			while (ntab--)
208
18
				vbl = term_tab_next(vbl);
209
210
			/* Re-establish indentation. */
211
212
7150
			if (p->flags & TERMP_BRIND)
213
819
				vbl += p->tcol->rmargin;
214
			else
215
6331
				vbl += p->tcol->offset;
216
20460
			maxvis = p->tcol->rmargin > vbl ?
217
6160
			    p->tcol->rmargin - vbl : 0;
218
20604
			bp = !(p->flags & TERMP_NOBREAK) ? maxvis :
219
1602
			    p->maxrmargin > vbl ?  p->maxrmargin - vbl : 0;
220
7150
		}
221
222
		/*
223
		 * Write out the rest of the word.
224
		 */
225
226
6639474
		for ( ; p->tcol->col < p->tcol->lastcol; p->tcol->col++) {
227

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

2048817
				while (p->tcol->col < p->tcol->lastcol &&
236
682795
				    p->tcol->buf[p->tcol->col] == ' ')
237
342766
					p->tcol->col++;
238
340173
				dv = (p->tcol->col - j) * (*p->width)(p, ' ');
239
340173
				vbl += dv;
240
340173
				vend += dv;
241
340173
				break;
242
			}
243
3052893
			if (p->tcol->buf[p->tcol->col] == ASCII_NBRSP) {
244
21581
				vbl += (*p->width)(p, ' ');
245
21581
				continue;
246
			}
247
3031312
			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
3031258
			if (vbl) {
256
493655
				(*p->advance)(p, vbl);
257
493655
				p->viscol += vbl;
258
				vbl = 0;
259
493655
			}
260
261
3031258
			(*p->letter)(p, p->tcol->buf[p->tcol->col]);
262
3031258
			if (p->tcol->buf[p->tcol->col] == '\b')
263
291429
				p->viscol -= (*p->width)(p,
264
291429
				    p->tcol->buf[p->tcol->col - 1]);
265
			else
266
2739829
				p->viscol += (*p->width)(p,
267
				    p->tcol->buf[p->tcol->col]);
268
		}
269
		vis = vend;
270
271
533616
		if (breakline == 0)
272
			continue;
273
274
		/* Explicitly requested output line break. */
275
276
36
		if (p->flags & TERMP_MULTICOL)
277
			return;
278
279
36
		endline(p);
280
		breakline = 0;
281
		vis = vend = 0;
282
283
		/* Re-establish indentation. */
284
285
36
		vbl = p->tcol->offset;
286
108
		maxvis = p->tcol->rmargin > vbl ?
287
36
		    p->tcol->rmargin - vbl : 0;
288
108
		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
186851
	if (vis > vbl)
298
186275
		vis -= vbl;
299
	else
300
		vis = 0;
301
302
186851
	p->col = p->tcol->col = p->tcol->lastcol = 0;
303
186851
	p->minbl = p->trailspace;
304
186851
	p->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE | TERMP_NOPAD);
305
306
186851
	if (p->flags & TERMP_MULTICOL)
307
55188
		return;
308
309
	/* Trailing whitespace is significant in some columns. */
310
311

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

171101
	if ((p->flags & TERMP_NOBREAK) == 0 ||
316
41238
	    ((p->flags & TERMP_HANG) == 0 &&
317
39438
	     vis + p->trailspace * (*p->width)(p, ' ') > maxvis))
318
100314
		endline(p);
319
318649
}
320
321
static void
322
endline(struct termp *p)
323
{
324
215000
	if ((p->flags & (TERMP_NEWMC | TERMP_ENDMC)) == TERMP_ENDMC) {
325
		p->mc = NULL;
326
		p->flags &= ~TERMP_ENDMC;
327
	}
328
107500
	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
107500
	p->viscol = 0;
336
107500
	p->minbl = 0;
337
107500
	(*p->endline)(p);
338
107500
}
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
256892
	p->flags |= TERMP_NOSPACE;
350

198937
	if (p->tcol->lastcol || p->viscol)
351
58171
		term_flushln(p);
352
128446
}
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
99396
	term_newln(p);
365
49698
	p->viscol = 0;
366
49698
	p->minbl = 0;
367
49698
	if (0 < p->skipvsp)
368
6885
		p->skipvsp--;
369
	else
370
42813
		(*p->endline)(p);
371
49698
}
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
1962
	f = p->fontl;
380
981
	p->fontl = p->fontq[p->fonti];
381
981
	p->fontq[p->fonti] = f;
382
981
}
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
291106
	p->fontl = p->fontq[p->fonti];
390
145553
	p->fontq[p->fonti] = f;
391
145553
}
392
393
/* Set font, save previous. */
394
void
395
term_fontpush(struct termp *p, enum termfont f)
396
{
397
398
53450
	p->fontl = p->fontq[p->fonti];
399
26725
	if (++p->fonti == p->fontsz) {
400
		p->fontsz += 8;
401
		p->fontq = mandoc_reallocarray(p->fontq,
402
		    p->fontsz, sizeof(*p->fontq));
403
	}
404
26725
	p->fontq[p->fonti] = f;
405
26725
}
406
407
/* Flush to make the saved pointer current again. */
408
void
409
term_fontpopq(struct termp *p, int i)
410
{
411
412
440512
	assert(i >= 0);
413
220256
	if (p->fonti > i)
414
21361
		p->fonti = i;
415
220256
}
416
417
/* Pop one font off the stack. */
418
void
419
term_fontpop(struct termp *p)
420
{
421
422
10728
	assert(p->fonti);
423
5364
	p->fonti--;
424
5364
}
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
508834
	struct roffsu	 su;
435
	const char	 nbrsp[2] = { ASCII_NBRSP, 0 };
436
508834
	const char	*seq, *cp;
437
508834
	int		 sz, uc;
438
508834
	size_t		 csz, lsz, ssz;
439
	enum mandoc_esc	 esc;
440
441
508834
	if ((p->flags & TERMP_NOBUF) == 0) {
442
508834
		if ((p->flags & TERMP_NOSPACE) == 0) {
443
54974
			if ((p->flags & TERMP_KEEP) == 0) {
444
50530
				bufferc(p, ' ');
445
50530
				if (p->flags & TERMP_SENTENCE)
446
1927
					bufferc(p, ' ');
447
			} else
448
4444
				bufferc(p, ASCII_NBRSP);
449
		}
450
508834
		if (p->flags & TERMP_PREKEEP)
451
7521
			p->flags |= TERMP_KEEP;
452
1017668
		if (p->flags & TERMP_NONOSPACE)
453
508834
			p->flags |= TERMP_NOSPACE;
454
		else
455
508834
			p->flags &= ~TERMP_NOSPACE;
456
508834
		p->flags &= ~(TERMP_SENTENCE | TERMP_NONEWLINE);
457
508834
		p->skipvsp = 0;
458
508834
	}
459
460
1094388
	while ('\0' != *word) {
461
585554
		if ('\\' != *word) {
462
522924
			if (TERMP_NBRWORD & p->flags) {
463
1512
				if (' ' == *word) {
464
495
					encode(p, nbrsp, 1);
465
495
					word++;
466
495
					continue;
467
				}
468
1017
				ssz = strcspn(word, "\\ ");
469
1017
			} else
470
521412
				ssz = strcspn(word, "\\");
471
522429
			encode(p, word, ssz);
472
522429
			word += (int)ssz;
473
522429
			continue;
474
		}
475
476
62630
		word++;
477
62630
		esc = mandoc_escape(&word, &seq, &sz);
478
62630
		if (ESCAPE_ERROR == esc)
479
			continue;
480
481




1090066
		switch (esc) {
482
		case ESCAPE_UNICODE:
483
6930
			uc = mchars_num2uc(seq + 1, sz - 1);
484
6930
			break;
485
		case ESCAPE_NUMBERED:
486
729
			uc = mchars_num2char(seq, sz);
487
729
			if (uc < 0)
488
				continue;
489
			break;
490
		case ESCAPE_SPECIAL:
491
28088
			if (p->enc == TERMENC_ASCII) {
492
24290
				cp = mchars_spec2str(seq, sz, &ssz);
493
24290
				if (cp != NULL)
494
24146
					encode(p, cp, ssz);
495
			} else {
496
3798
				uc = mchars_spec2cp(seq, sz);
497
3798
				if (uc > 0)
498
3618
					encode1(p, uc);
499
			}
500
			continue;
501
		case ESCAPE_FONTBOLD:
502
7074
			term_fontrepl(p, TERMFONT_BOLD);
503
7074
			continue;
504
		case ESCAPE_FONTITALIC:
505
4923
			term_fontrepl(p, TERMFONT_UNDER);
506
4923
			continue;
507
		case ESCAPE_FONTBI:
508
18
			term_fontrepl(p, TERMFONT_BI);
509
18
			continue;
510
		case ESCAPE_FONT:
511
		case ESCAPE_FONTROMAN:
512
13401
			term_fontrepl(p, TERMFONT_NONE);
513
13401
			continue;
514
		case ESCAPE_FONTPREV:
515
927
			term_fontlast(p);
516
927
			continue;
517
		case ESCAPE_BREAK:
518
36
			bufferc(p, '\n');
519
36
			continue;
520
		case ESCAPE_NOSPACE:
521
63
			if (p->flags & TERMP_BACKAFTER)
522
9
				p->flags &= ~TERMP_BACKAFTER;
523
54
			else if (*word == '\0')
524
54
				p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE);
525
			continue;
526
		case ESCAPE_HORIZ:
527
63
			if (*seq == '|') {
528
9
				seq++;
529
9
				uc = -p->col;
530
9
			} else
531
				uc = 0;
532
63
			if (a2roffsu(seq, &su, SCALE_EM) == NULL)
533
				continue;
534
63
			uc += term_hen(p, &su);
535
63
			if (uc > 0)
536
72
				while (uc-- > 0)
537
27
					bufferc(p, ASCII_NBRSP);
538
45
			else if (p->col > (size_t)(-uc))
539
45
				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
			continue;
552
		case ESCAPE_HLINE:
553
63
			if ((cp = a2roffsu(seq, &su, SCALE_EM)) == NULL)
554
				continue;
555
63
			uc = term_hen(p, &su);
556
63
			if (uc <= 0) {
557
				if (p->tcol->rmargin <= p->tcol->offset)
558
					continue;
559
				lsz = p->tcol->rmargin - p->tcol->offset;
560
			} else
561
63
				lsz = uc;
562
63
			if (*cp == seq[-1])
563
18
				uc = -1;
564
45
			else if (*cp == '\\') {
565
18
				seq = cp + 1;
566
18
				esc = mandoc_escape(&seq, &cp, &sz);
567

18
				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
18
					uc = mchars_spec2cp(cp, sz);
576
18
					break;
577
				default:
578
					uc = -1;
579
					break;
580
				}
581
			} else
582
				uc = *cp;
583

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

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

56964
	if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
667
56964
		p->tcol->buf[p->col] = c;
668
56964
	if (p->tcol->lastcol < ++p->col)
669
56964
		p->tcol->lastcol = p->col;
670
56964
}
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
4902128
	if (p->flags & TERMP_NOBUF) {
683
		(*p->letter)(p, c);
684
		return;
685
	}
686
687
2451064
	if (p->col + 7 >= p->tcol->maxcols)
688
9
		adjbuf(p->tcol, p->col + 7);
689
690

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

234
		if (p->tcol->buf[p->col - 1] == ' ' ||
695
117
		    p->tcol->buf[p->col - 1] == '\t')
696
			p->col--;
697
		else
698
117
			p->tcol->buf[p->col++] = '\b';
699
117
		p->flags &= ~TERMP_BACKBEFORE;
700
117
	}
701
2451064
	if (f == TERMFONT_UNDER || f == TERMFONT_BI) {
702
72920
		p->tcol->buf[p->col++] = '_';
703
72920
		p->tcol->buf[p->col++] = '\b';
704
72920
	}
705
2451064
	if (f == TERMFONT_BOLD || f == TERMFONT_BI) {
706
215863
		if (c == ASCII_HYPH)
707
2232
			p->tcol->buf[p->col++] = '-';
708
		else
709
213631
			p->tcol->buf[p->col++] = c;
710
215863
		p->tcol->buf[p->col++] = '\b';
711
215863
	}
712

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

5516093
		if (ASCII_HYPH == word[i] ||
738
2755189
		    isgraph((unsigned char)word[i]))
739
2443468
			encode1(p, word[i]);
740
		else {
741

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

319965
			if (word[i] == '\b' &&
752
2529
			    (p->flags & TERMP_BACKBEFORE)) {
753
				p->flags &= ~TERMP_BACKBEFORE;
754
				p->flags |= TERMP_BACKAFTER;
755
			}
756
		}
757
	}
758
551075
	if (p->tcol->lastcol < p->col)
759
24375
		p->tcol->lastcol = p->col;
760
1102150
}
761
762
void
763
term_setwidth(struct termp *p, const char *wstr)
764
{
765
180
	struct roffsu	 su;
766
	int		 iop, width;
767
768
	iop = 0;
769
	width = 0;
770
90
	if (NULL != wstr) {
771
90
		switch (*wstr) {
772
		case '+':
773
			iop = 1;
774
18
			wstr++;
775
18
			break;
776
		case '-':
777
			iop = -1;
778
			wstr++;
779
			break;
780
		default:
781
			break;
782
		}
783
72
		if (a2roffsu(wstr, &su, SCALE_MAX) != NULL)
784
54
			width = term_hspan(p, &su);
785
		else
786
			iop = 0;
787
	}
788
90
	(*p->setwidth)(p, iop, width);
789
90
}
790
791
size_t
792
term_len(const struct termp *p, size_t sz)
793
{
794
795
138078
	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
1854396
	if (*skip) {
803
		(*skip) = 0;
804
		return 0;
805
	} else
806
927198
		return (*p->width)(p, c);
807
927198
}
808
809
size_t
810
term_strlen(const struct termp *p, const char *cp)
811
{
812
347450
	size_t		 sz, rsz, i;
813
347450
	int		 ssz, skip, uc;
814
347450
	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
347450
	skip = 0;
827
1043151
	while ('\0' != *cp) {
828
348251
		rsz = strcspn(cp, rej);
829
2352250
		for (i = 0; i < rsz; i++)
830
827874
			sz += cond_width(p, *cp++, &skip);
831
832

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

3726
			switch (esc) {
842
			case ESCAPE_UNICODE:
843
1296
				uc = mchars_num2uc(seq + 1, ssz - 1);
844
1296
				break;
845
			case ESCAPE_NUMBERED:
846
				uc = mchars_num2char(seq, ssz);
847
				if (uc < 0)
848
					continue;
849
				break;
850
			case ESCAPE_SPECIAL:
851
252
				if (p->enc == TERMENC_ASCII) {
852
126
					rhs = mchars_spec2str(seq, ssz, &rsz);
853
126
					if (rhs != NULL)
854
						break;
855
				} else {
856
126
					uc = mchars_spec2cp(seq, ssz);
857
126
					if (uc > 0)
858
						sz += cond_width(p, uc, &skip);
859
				}
860
				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
				continue;
880
			}
881
882
			/*
883
			 * Common handling for Unicode and numbered
884
			 * character escape sequences.
885
			 */
886
887
1296
			if (rhs == NULL) {
888
1296
				if (p->enc == TERMENC_ASCII) {
889
648
					rhs = ascii_uc2str(uc);
890
648
					rsz = strlen(rhs);
891
				} else {
892
648
					if ((uc < 0x20 && uc != 0x09) ||
893
612
					    (uc > 0x7E && uc < 0xA0))
894
90
						uc = 0xFFFD;
895
648
					sz += cond_width(p, uc, &skip);
896
648
					continue;
897
				}
898
648
			}
899
900
648
			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
5436
			for (i = 0; i < rsz; i++)
911
2070
				sz += (*p->width)(p, *rhs++);
912
			break;
913
		case ASCII_NBRSP:
914
98676
			sz += cond_width(p, ' ', &skip);
915
98676
			cp++;
916
98676
			break;
917
		case ASCII_HYPH:
918
			sz += cond_width(p, '-', &skip);
919
			cp++;
920
			break;
921
		default:
922
			break;
923
		}
924
	}
925
926
347450
	return sz;
927
347450
}
928
929
int
930
term_vspan(const struct termp *p, const struct roffsu *su)
931
{
932
	double		 r;
933
	int		 ri;
934
935


6534
	switch (su->unit) {
936
	case SCALE_BU:
937
36
		r = su->scale / 40.0;
938
36
		break;
939
	case SCALE_CM:
940
27
		r = su->scale * 6.0 / 2.54;
941
27
		break;
942
	case SCALE_FS:
943
		r = su->scale * 65536.0 / 40.0;
944
		break;
945
	case SCALE_IN:
946
18
		r = su->scale * 6.0;
947
18
		break;
948
	case SCALE_MM:
949
		r = su->scale * 0.006;
950
		break;
951
	case SCALE_PC:
952
36
		r = su->scale;
953
36
		break;
954
	case SCALE_PT:
955
45
		r = su->scale / 12.0;
956
45
		break;
957
	case SCALE_EN:
958
	case SCALE_EM:
959
54
		r = su->scale * 0.6;
960
54
		break;
961
	case SCALE_VS:
962
3051
		r = su->scale;
963
3051
		break;
964
	default:
965
		abort();
966
	}
967
3267
	ri = r > 0.0 ? r + 0.4995 : r - 0.4995;
968
3267
	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
108
	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
71916
	if ((bu = (*p->hspan)(p, su)) >= 0)
990
35175
		return (bu + 11) / 24;
991
	else
992
783
		return -((-bu + 11) / 24);
993
35958
}