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

Line Branch Exec Source
1
/*	$OpenBSD: term.c,v 1.117 2016/03/20 16:50:30 krw Exp $ */
2
/*
3
 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4
 * Copyright (c) 2010-2015 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 *p, 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
38
39
void
40
term_free(struct termp *p)
41
{
42
43
	free(p->buf);
44
	free(p->fontq);
45
	free(p);
46
}
47
48
void
49
term_begin(struct termp *p, term_margin head,
50
		term_margin foot, const struct roff_meta *arg)
51
{
52
53
	p->headf = head;
54
	p->footf = foot;
55
	p->argf = arg;
56
	(*p->begin)(p);
57
}
58
59
void
60
term_end(struct termp *p)
61
{
62
63
	(*p->end)(p);
64
}
65
66
/*
67
 * Flush a chunk of text.  By default, break the output line each time
68
 * the right margin is reached, and continue output on the next line
69
 * at the same offset as the chunk itself.  By default, also break the
70
 * output line at the end of the chunk.
71
 * The following flags may be specified:
72
 *
73
 *  - TERMP_NOBREAK: Do not break the output line at the right margin,
74
 *    but only at the max right margin.  Also, do not break the output
75
 *    line at the end of the chunk, such that the next call can pad to
76
 *    the next column.  However, if less than p->trailspace blanks,
77
 *    which can be 0, 1, or 2, remain to the right margin, the line
78
 *    will be broken.
79
 *  - TERMP_BRTRSP: Consider trailing whitespace significant
80
 *    when deciding whether the chunk fits or not.
81
 *  - TERMP_BRIND: If the chunk does not fit and the output line has
82
 *    to be broken, start the next line at the right margin instead
83
 *    of at the offset.  Used together with TERMP_NOBREAK for the tags
84
 *    in various kinds of tagged lists.
85
 *  - TERMP_DANGLE: Do not break the output line at the right margin,
86
 *    append the next chunk after it even if this one is too long.
87
 *    To be used together with TERMP_NOBREAK.
88
 *  - TERMP_HANG: Like TERMP_DANGLE, and also suppress padding before
89
 *    the next chunk if this column is not full.
90
 */
91
void
92
term_flushln(struct termp *p)
93
{
94
	size_t		 i;     /* current input position in p->buf */
95
	int		 ntab;	/* number of tabs to prepend */
96
	size_t		 vis;   /* current visual position on output */
97
	size_t		 vbl;   /* number of blanks to prepend to output */
98
	size_t		 vend;	/* end of word visual position on output */
99
	size_t		 bp;    /* visual right border position */
100
	size_t		 dv;    /* temporary for visual pos calculations */
101
	size_t		 j;     /* temporary loop index for p->buf */
102
	size_t		 jhy;	/* last hyph before overflow w/r/t j */
103
	size_t		 maxvis; /* output position of visible boundary */
104
105
	/*
106
	 * First, establish the maximum columns of "visible" content.
107
	 * This is usually the difference between the right-margin and
108
	 * an indentation, but can be, for tagged lists or columns, a
109
	 * small set of values.
110
	 *
111
	 * The following unsigned-signed subtractions look strange,
112
	 * but they are actually correct.  If the int p->overstep
113
	 * is negative, it gets sign extended.  Subtracting that
114
	 * very large size_t effectively adds a small number to dv.
115
	 */
116
	dv = p->rmargin > p->offset ? p->rmargin - p->offset : 0;
117
	maxvis = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0;
118
119
	if (p->flags & TERMP_NOBREAK) {
120
		dv = p->maxrmargin > p->offset ?
121
		     p->maxrmargin - p->offset : 0;
122
		bp = (int)dv > p->overstep ?
123
		     dv - (size_t)p->overstep : 0;
124
	} else
125
		bp = maxvis;
126
127
	/*
128
	 * Calculate the required amount of padding.
129
	 */
130
	vbl = p->offset + p->overstep > p->viscol ?
131
	      p->offset + p->overstep - p->viscol : 0;
132
133
	vis = vend = 0;
134
	i = 0;
135
136
	while (i < p->col) {
137
		/*
138
		 * Handle literal tab characters: collapse all
139
		 * subsequent tabs into a single huge set of spaces.
140
		 */
141
		ntab = 0;
142
		while (i < p->col && '\t' == p->buf[i]) {
143
			vend = (vis / p->tabwidth + 1) * p->tabwidth;
144
			vbl += vend - vis;
145
			vis = vend;
146
			ntab++;
147
			i++;
148
		}
149
150
		/*
151
		 * Count up visible word characters.  Control sequences
152
		 * (starting with the CSI) aren't counted.  A space
153
		 * generates a non-printing word, which is valid (the
154
		 * space is printed according to regular spacing rules).
155
		 */
156
157
		for (j = i, jhy = 0; j < p->col; j++) {
158
			if (' ' == p->buf[j] || '\t' == p->buf[j])
159
				break;
160
161
			/* Back over the last printed character. */
162
			if (8 == p->buf[j]) {
163
				assert(j);
164
				vend -= (*p->width)(p, p->buf[j - 1]);
165
				continue;
166
			}
167
168
			/* Regular word. */
169
			/* Break at the hyphen point if we overrun. */
170
			if (vend > vis && vend < bp &&
171
			    (ASCII_HYPH == p->buf[j] ||
172
			     ASCII_BREAK == p->buf[j]))
173
				jhy = j;
174
175
			/*
176
			 * Hyphenation now decided, put back a real
177
			 * hyphen such that we get the correct width.
178
			 */
179
			if (ASCII_HYPH == p->buf[j])
180
				p->buf[j] = '-';
181
182
			vend += (*p->width)(p, p->buf[j]);
183
		}
184
185
		/*
186
		 * Find out whether we would exceed the right margin.
187
		 * If so, break to the next line.
188
		 */
189
		if (vend > bp && 0 == jhy && vis > 0) {
190
			vend -= vis;
191
			(*p->endline)(p);
192
			p->viscol = 0;
193
			if (TERMP_BRIND & p->flags) {
194
				vbl = p->rmargin;
195
				vend += p->rmargin;
196
				vend -= p->offset;
197
			} else
198
				vbl = p->offset;
199
200
			/* use pending tabs on the new line */
201
202
			if (0 < ntab)
203
				vbl += ntab * p->tabwidth;
204
205
			/*
206
			 * Remove the p->overstep width.
207
			 * Again, if p->overstep is negative,
208
			 * sign extension does the right thing.
209
			 */
210
211
			bp += (size_t)p->overstep;
212
			p->overstep = 0;
213
		}
214
215
		/* Write out the [remaining] word. */
216
		for ( ; i < p->col; i++) {
217
			if (vend > bp && jhy > 0 && i > jhy)
218
				break;
219
			if ('\t' == p->buf[i])
220
				break;
221
			if (' ' == p->buf[i]) {
222
				j = i;
223
				while (i < p->col && ' ' == p->buf[i])
224
					i++;
225
				dv = (i - j) * (*p->width)(p, ' ');
226
				vbl += dv;
227
				vend += dv;
228
				break;
229
			}
230
			if (ASCII_NBRSP == p->buf[i]) {
231
				vbl += (*p->width)(p, ' ');
232
				continue;
233
			}
234
			if (ASCII_BREAK == p->buf[i])
235
				continue;
236
237
			/*
238
			 * Now we definitely know there will be
239
			 * printable characters to output,
240
			 * so write preceding white space now.
241
			 */
242
			if (vbl) {
243
				(*p->advance)(p, vbl);
244
				p->viscol += vbl;
245
				vbl = 0;
246
			}
247
248
			(*p->letter)(p, p->buf[i]);
249
			if (8 == p->buf[i])
250
				p->viscol -= (*p->width)(p, p->buf[i-1]);
251
			else
252
				p->viscol += (*p->width)(p, p->buf[i]);
253
		}
254
		vis = vend;
255
	}
256
257
	/*
258
	 * If there was trailing white space, it was not printed;
259
	 * so reset the cursor position accordingly.
260
	 */
261
	if (vis > vbl)
262
		vis -= vbl;
263
	else
264
		vis = 0;
265
266
	p->col = 0;
267
	p->overstep = 0;
268
	p->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE);
269
270
	if ( ! (TERMP_NOBREAK & p->flags)) {
271
		p->viscol = 0;
272
		(*p->endline)(p);
273
		return;
274
	}
275
276
	if (TERMP_HANG & p->flags) {
277
		p->overstep += (int)(p->offset + vis - p->rmargin +
278
		    p->trailspace * (*p->width)(p, ' '));
279
280
		/*
281
		 * If we have overstepped the margin, temporarily move
282
		 * it to the right and flag the rest of the line to be
283
		 * shorter.
284
		 * If there is a request to keep the columns together,
285
		 * allow negative overstep when the column is not full.
286
		 */
287
		if (p->trailspace && p->overstep < 0)
288
			p->overstep = 0;
289
		return;
290
291
	} else if (TERMP_DANGLE & p->flags)
292
		return;
293
294
	/* Trailing whitespace is significant in some columns. */
295
	if (vis && vbl && (TERMP_BRTRSP & p->flags))
296
		vis += vbl;
297
298
	/* If the column was overrun, break the line. */
299
	if (maxvis < vis + p->trailspace * (*p->width)(p, ' ')) {
300
		(*p->endline)(p);
301
		p->viscol = 0;
302
	}
303
}
304
305
/*
306
 * A newline only breaks an existing line; it won't assert vertical
307
 * space.  All data in the output buffer is flushed prior to the newline
308
 * assertion.
309
 */
310
void
311
term_newln(struct termp *p)
312
{
313
314
	p->flags |= TERMP_NOSPACE;
315
	if (p->col || p->viscol)
316
		term_flushln(p);
317
}
318
319
/*
320
 * Asserts a vertical space (a full, empty line-break between lines).
321
 * Note that if used twice, this will cause two blank spaces and so on.
322
 * All data in the output buffer is flushed prior to the newline
323
 * assertion.
324
 */
325
void
326
term_vspace(struct termp *p)
327
{
328
329
	term_newln(p);
330
	p->viscol = 0;
331
	if (0 < p->skipvsp)
332
		p->skipvsp--;
333
	else
334
		(*p->endline)(p);
335
}
336
337
/* Swap current and previous font; for \fP and .ft P */
338
void
339
term_fontlast(struct termp *p)
340
{
341
	enum termfont	 f;
342
343
	f = p->fontl;
344
	p->fontl = p->fontq[p->fonti];
345
	p->fontq[p->fonti] = f;
346
}
347
348
/* Set font, save current, discard previous; for \f, .ft, .B etc. */
349
void
350
term_fontrepl(struct termp *p, enum termfont f)
351
{
352
353
	p->fontl = p->fontq[p->fonti];
354
	p->fontq[p->fonti] = f;
355
}
356
357
/* Set font, save previous. */
358
void
359
term_fontpush(struct termp *p, enum termfont f)
360
{
361
362
	p->fontl = p->fontq[p->fonti];
363
	if (++p->fonti == p->fontsz) {
364
		p->fontsz += 8;
365
		p->fontq = mandoc_reallocarray(p->fontq,
366
		    p->fontsz, sizeof(*p->fontq));
367
	}
368
	p->fontq[p->fonti] = f;
369
}
370
371
/* Flush to make the saved pointer current again. */
372
void
373
term_fontpopq(struct termp *p, int i)
374
{
375
376
	assert(i >= 0);
377
	if (p->fonti > i)
378
		p->fonti = i;
379
}
380
381
/* Pop one font off the stack. */
382
void
383
term_fontpop(struct termp *p)
384
{
385
386
	assert(p->fonti);
387
	p->fonti--;
388
}
389
390
/*
391
 * Handle pwords, partial words, which may be either a single word or a
392
 * phrase that cannot be broken down (such as a literal string).  This
393
 * handles word styling.
394
 */
395
void
396
term_word(struct termp *p, const char *word)
397
{
398
	const char	 nbrsp[2] = { ASCII_NBRSP, 0 };
399
	const char	*seq, *cp;
400
	int		 sz, uc;
401
	size_t		 ssz;
402
	enum mandoc_esc	 esc;
403
404
	if ( ! (TERMP_NOSPACE & p->flags)) {
405
		if ( ! (TERMP_KEEP & p->flags)) {
406
			bufferc(p, ' ');
407
			if (TERMP_SENTENCE & p->flags)
408
				bufferc(p, ' ');
409
		} else
410
			bufferc(p, ASCII_NBRSP);
411
	}
412
	if (TERMP_PREKEEP & p->flags)
413
		p->flags |= TERMP_KEEP;
414
415
	if ( ! (p->flags & TERMP_NONOSPACE))
416
		p->flags &= ~TERMP_NOSPACE;
417
	else
418
		p->flags |= TERMP_NOSPACE;
419
420
	p->flags &= ~(TERMP_SENTENCE | TERMP_NONEWLINE);
421
	p->skipvsp = 0;
422
423
	while ('\0' != *word) {
424
		if ('\\' != *word) {
425
			if (TERMP_NBRWORD & p->flags) {
426
				if (' ' == *word) {
427
					encode(p, nbrsp, 1);
428
					word++;
429
					continue;
430
				}
431
				ssz = strcspn(word, "\\ ");
432
			} else
433
				ssz = strcspn(word, "\\");
434
			encode(p, word, ssz);
435
			word += (int)ssz;
436
			continue;
437
		}
438
439
		word++;
440
		esc = mandoc_escape(&word, &seq, &sz);
441
		if (ESCAPE_ERROR == esc)
442
			continue;
443
444
		switch (esc) {
445
		case ESCAPE_UNICODE:
446
			uc = mchars_num2uc(seq + 1, sz - 1);
447
			break;
448
		case ESCAPE_NUMBERED:
449
			uc = mchars_num2char(seq, sz);
450
			if (uc < 0)
451
				continue;
452
			break;
453
		case ESCAPE_SPECIAL:
454
			if (p->enc == TERMENC_ASCII) {
455
				cp = mchars_spec2str(seq, sz, &ssz);
456
				if (cp != NULL)
457
					encode(p, cp, ssz);
458
			} else {
459
				uc = mchars_spec2cp(seq, sz);
460
				if (uc > 0)
461
					encode1(p, uc);
462
			}
463
			continue;
464
		case ESCAPE_FONTBOLD:
465
			term_fontrepl(p, TERMFONT_BOLD);
466
			continue;
467
		case ESCAPE_FONTITALIC:
468
			term_fontrepl(p, TERMFONT_UNDER);
469
			continue;
470
		case ESCAPE_FONTBI:
471
			term_fontrepl(p, TERMFONT_BI);
472
			continue;
473
		case ESCAPE_FONT:
474
		case ESCAPE_FONTROMAN:
475
			term_fontrepl(p, TERMFONT_NONE);
476
			continue;
477
		case ESCAPE_FONTPREV:
478
			term_fontlast(p);
479
			continue;
480
		case ESCAPE_NOSPACE:
481
			if (p->flags & TERMP_BACKAFTER)
482
				p->flags &= ~TERMP_BACKAFTER;
483
			else if (*word == '\0')
484
				p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE);
485
			continue;
486
		case ESCAPE_SKIPCHAR:
487
			p->flags |= TERMP_BACKAFTER;
488
			continue;
489
		case ESCAPE_OVERSTRIKE:
490
			cp = seq + sz;
491
			while (seq < cp) {
492
				if (*seq == '\\') {
493
					mandoc_escape(&seq, NULL, NULL);
494
					continue;
495
				}
496
				encode1(p, *seq++);
497
				if (seq < cp) {
498
					if (p->flags & TERMP_BACKBEFORE)
499
						p->flags |= TERMP_BACKAFTER;
500
					else
501
						p->flags |= TERMP_BACKBEFORE;
502
				}
503
			}
504
			/* Trim trailing backspace/blank pair. */
505
			if (p->col > 2 && p->buf[p->col - 1] == ' ')
506
				p->col -= 2;
507
			continue;
508
		default:
509
			continue;
510
		}
511
512
		/*
513
		 * Common handling for Unicode and numbered
514
		 * character escape sequences.
515
		 */
516
517
		if (p->enc == TERMENC_ASCII) {
518
			cp = ascii_uc2str(uc);
519
			encode(p, cp, strlen(cp));
520
		} else {
521
			if ((uc < 0x20 && uc != 0x09) ||
522
			    (uc > 0x7E && uc < 0xA0))
523
				uc = 0xFFFD;
524
			encode1(p, uc);
525
		}
526
	}
527
	p->flags &= ~TERMP_NBRWORD;
528
}
529
530
static void
531
adjbuf(struct termp *p, size_t sz)
532
{
533
534
	if (0 == p->maxcols)
535
		p->maxcols = 1024;
536
	while (sz >= p->maxcols)
537
		p->maxcols <<= 2;
538
539
	p->buf = mandoc_reallocarray(p->buf, p->maxcols, sizeof(int));
540
}
541
542
static void
543
bufferc(struct termp *p, char c)
544
{
545
546
	if (p->col + 1 >= p->maxcols)
547
		adjbuf(p, p->col + 1);
548
549
	p->buf[p->col++] = c;
550
}
551
552
/*
553
 * See encode().
554
 * Do this for a single (probably unicode) value.
555
 * Does not check for non-decorated glyphs.
556
 */
557
static void
558
encode1(struct termp *p, int c)
559
{
560
	enum termfont	  f;
561
562
	if (p->col + 7 >= p->maxcols)
563
		adjbuf(p, p->col + 7);
564
565
	f = (c == ASCII_HYPH || c > 127 || isgraph(c)) ?
566
	    p->fontq[p->fonti] : TERMFONT_NONE;
567
568
	if (p->flags & TERMP_BACKBEFORE) {
569
		if (p->buf[p->col - 1] == ' ')
570
			p->col--;
571
		else
572
			p->buf[p->col++] = 8;
573
		p->flags &= ~TERMP_BACKBEFORE;
574
	}
575
	if (TERMFONT_UNDER == f || TERMFONT_BI == f) {
576
		p->buf[p->col++] = '_';
577
		p->buf[p->col++] = 8;
578
	}
579
	if (TERMFONT_BOLD == f || TERMFONT_BI == f) {
580
		if (ASCII_HYPH == c)
581
			p->buf[p->col++] = '-';
582
		else
583
			p->buf[p->col++] = c;
584
		p->buf[p->col++] = 8;
585
	}
586
	p->buf[p->col++] = c;
587
	if (p->flags & TERMP_BACKAFTER) {
588
		p->flags |= TERMP_BACKBEFORE;
589
		p->flags &= ~TERMP_BACKAFTER;
590
	}
591
}
592
593
static void
594
encode(struct termp *p, const char *word, size_t sz)
595
{
596
	size_t		  i;
597
598
	if (p->col + 2 + (sz * 5) >= p->maxcols)
599
		adjbuf(p, p->col + 2 + (sz * 5));
600
601
	for (i = 0; i < sz; i++) {
602
		if (ASCII_HYPH == word[i] ||
603
		    isgraph((unsigned char)word[i]))
604
			encode1(p, word[i]);
605
		else
606
			p->buf[p->col++] = word[i];
607
	}
608
}
609
610
void
611
term_setwidth(struct termp *p, const char *wstr)
612
{
613
	struct roffsu	 su;
614
	int		 iop, width;
615
616
	iop = 0;
617
	width = 0;
618
	if (NULL != wstr) {
619
		switch (*wstr) {
620
		case '+':
621
			iop = 1;
622
			wstr++;
623
			break;
624
		case '-':
625
			iop = -1;
626
			wstr++;
627
			break;
628
		default:
629
			break;
630
		}
631
		if (a2roffsu(wstr, &su, SCALE_MAX))
632
			width = term_hspan(p, &su);
633
		else
634
			iop = 0;
635
	}
636
	(*p->setwidth)(p, iop, width);
637
}
638
639
size_t
640
term_len(const struct termp *p, size_t sz)
641
{
642
643
	return (*p->width)(p, ' ') * sz;
644
}
645
646
static size_t
647
cond_width(const struct termp *p, int c, int *skip)
648
{
649
650
	if (*skip) {
651
		(*skip) = 0;
652
		return 0;
653
	} else
654
		return (*p->width)(p, c);
655
}
656
657
size_t
658
term_strlen(const struct termp *p, const char *cp)
659
{
660
	size_t		 sz, rsz, i;
661
	int		 ssz, skip, uc;
662
	const char	*seq, *rhs;
663
	enum mandoc_esc	 esc;
664
	static const char rej[] = { '\\', ASCII_NBRSP, ASCII_HYPH,
665
			ASCII_BREAK, '\0' };
666
667
	/*
668
	 * Account for escaped sequences within string length
669
	 * calculations.  This follows the logic in term_word() as we
670
	 * must calculate the width of produced strings.
671
	 */
672
673
	sz = 0;
674
	skip = 0;
675
	while ('\0' != *cp) {
676
		rsz = strcspn(cp, rej);
677
		for (i = 0; i < rsz; i++)
678
			sz += cond_width(p, *cp++, &skip);
679
680
		switch (*cp) {
681
		case '\\':
682
			cp++;
683
			esc = mandoc_escape(&cp, &seq, &ssz);
684
			if (ESCAPE_ERROR == esc)
685
				continue;
686
687
			rhs = NULL;
688
689
			switch (esc) {
690
			case ESCAPE_UNICODE:
691
				uc = mchars_num2uc(seq + 1, ssz - 1);
692
				break;
693
			case ESCAPE_NUMBERED:
694
				uc = mchars_num2char(seq, ssz);
695
				if (uc < 0)
696
					continue;
697
				break;
698
			case ESCAPE_SPECIAL:
699
				if (p->enc == TERMENC_ASCII) {
700
					rhs = mchars_spec2str(seq, ssz, &rsz);
701
					if (rhs != NULL)
702
						break;
703
				} else {
704
					uc = mchars_spec2cp(seq, ssz);
705
					if (uc > 0)
706
						sz += cond_width(p, uc, &skip);
707
				}
708
				continue;
709
			case ESCAPE_SKIPCHAR:
710
				skip = 1;
711
				continue;
712
			case ESCAPE_OVERSTRIKE:
713
				rsz = 0;
714
				rhs = seq + ssz;
715
				while (seq < rhs) {
716
					if (*seq == '\\') {
717
						mandoc_escape(&seq, NULL, NULL);
718
						continue;
719
					}
720
					i = (*p->width)(p, *seq++);
721
					if (rsz < i)
722
						rsz = i;
723
				}
724
				sz += rsz;
725
				continue;
726
			default:
727
				continue;
728
			}
729
730
			/*
731
			 * Common handling for Unicode and numbered
732
			 * character escape sequences.
733
			 */
734
735
			if (rhs == NULL) {
736
				if (p->enc == TERMENC_ASCII) {
737
					rhs = ascii_uc2str(uc);
738
					rsz = strlen(rhs);
739
				} else {
740
					if ((uc < 0x20 && uc != 0x09) ||
741
					    (uc > 0x7E && uc < 0xA0))
742
						uc = 0xFFFD;
743
					sz += cond_width(p, uc, &skip);
744
					continue;
745
				}
746
			}
747
748
			if (skip) {
749
				skip = 0;
750
				break;
751
			}
752
753
			/*
754
			 * Common handling for all escape sequences
755
			 * printing more than one character.
756
			 */
757
758
			for (i = 0; i < rsz; i++)
759
				sz += (*p->width)(p, *rhs++);
760
			break;
761
		case ASCII_NBRSP:
762
			sz += cond_width(p, ' ', &skip);
763
			cp++;
764
			break;
765
		case ASCII_HYPH:
766
			sz += cond_width(p, '-', &skip);
767
			cp++;
768
			break;
769
		default:
770
			break;
771
		}
772
	}
773
774
	return sz;
775
}
776
777
int
778
term_vspan(const struct termp *p, const struct roffsu *su)
779
{
780
	double		 r;
781
	int		 ri;
782
783
	switch (su->unit) {
784
	case SCALE_BU:
785
		r = su->scale / 40.0;
786
		break;
787
	case SCALE_CM:
788
		r = su->scale * 6.0 / 2.54;
789
		break;
790
	case SCALE_FS:
791
		r = su->scale * 65536.0 / 40.0;
792
		break;
793
	case SCALE_IN:
794
		r = su->scale * 6.0;
795
		break;
796
	case SCALE_MM:
797
		r = su->scale * 0.006;
798
		break;
799
	case SCALE_PC:
800
		r = su->scale;
801
		break;
802
	case SCALE_PT:
803
		r = su->scale / 12.0;
804
		break;
805
	case SCALE_EN:
806
	case SCALE_EM:
807
		r = su->scale * 0.6;
808
		break;
809
	case SCALE_VS:
810
		r = su->scale;
811
		break;
812
	default:
813
		abort();
814
	}
815
	ri = r > 0.0 ? r + 0.4995 : r - 0.4995;
816
	return ri < 66 ? ri : 1;
817
}
818
819
/*
820
 * Convert a scaling width to basic units, rounding down.
821
 */
822
int
823
term_hspan(const struct termp *p, const struct roffsu *su)
824
{
825
826
	return (*p->hspan)(p, su);
827
}