xref: /freebsd/contrib/mandoc/term.c (revision 732a02b4e77866604a120a275c082bb6221bd2ff)
1 /*	$Id: term.c,v 1.281 2019/06/03 20:23:41 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2010-2019 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 "config.h"
19 
20 #include <sys/types.h>
21 
22 #include <assert.h>
23 #include <ctype.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include "mandoc.h"
30 #include "mandoc_aux.h"
31 #include "out.h"
32 #include "term.h"
33 #include "main.h"
34 
35 static	size_t		 cond_width(const struct termp *, int, int *);
36 static	void		 adjbuf(struct termp_col *, size_t);
37 static	void		 bufferc(struct termp *, char);
38 static	void		 encode(struct termp *, const char *, size_t);
39 static	void		 encode1(struct termp *, int);
40 static	void		 endline(struct termp *);
41 static	void		 term_field(struct termp *, size_t, size_t,
42 				size_t, size_t);
43 static	void		 term_fill(struct termp *, size_t *, size_t *,
44 				size_t);
45 
46 
47 void
48 term_setcol(struct termp *p, size_t maxtcol)
49 {
50 	if (maxtcol > p->maxtcol) {
51 		p->tcols = mandoc_recallocarray(p->tcols,
52 		    p->maxtcol, maxtcol, sizeof(*p->tcols));
53 		p->maxtcol = maxtcol;
54 	}
55 	p->lasttcol = maxtcol - 1;
56 	p->tcol = p->tcols;
57 }
58 
59 void
60 term_free(struct termp *p)
61 {
62 	for (p->tcol = p->tcols; p->tcol < p->tcols + p->maxtcol; p->tcol++)
63 		free(p->tcol->buf);
64 	free(p->tcols);
65 	free(p->fontq);
66 	free(p);
67 }
68 
69 void
70 term_begin(struct termp *p, term_margin head,
71 		term_margin foot, const struct roff_meta *arg)
72 {
73 
74 	p->headf = head;
75 	p->footf = foot;
76 	p->argf = arg;
77 	(*p->begin)(p);
78 }
79 
80 void
81 term_end(struct termp *p)
82 {
83 
84 	(*p->end)(p);
85 }
86 
87 /*
88  * Flush a chunk of text.  By default, break the output line each time
89  * the right margin is reached, and continue output on the next line
90  * at the same offset as the chunk itself.  By default, also break the
91  * output line at the end of the chunk.  There are many flags modifying
92  * this behaviour, see the comments in the body of the function.
93  */
94 void
95 term_flushln(struct termp *p)
96 {
97 	size_t	 vbl;      /* Number of blanks to prepend to the output. */
98 	size_t	 vbr;      /* Actual visual position of the end of field. */
99 	size_t	 vfield;   /* Desired visual field width. */
100 	size_t	 vtarget;  /* Desired visual position of the right margin. */
101 	size_t	 ic;       /* Character position in the input buffer. */
102 	size_t	 nbr;      /* Number of characters to print in this field. */
103 
104 	/*
105 	 * Normally, start writing at the left margin, but with the
106 	 * NOPAD flag, start writing at the current position instead.
107 	 */
108 
109 	vbl = (p->flags & TERMP_NOPAD) || p->tcol->offset < p->viscol ?
110 	    0 : p->tcol->offset - p->viscol;
111 	if (p->minbl && vbl < p->minbl)
112 		vbl = p->minbl;
113 
114 	if ((p->flags & TERMP_MULTICOL) == 0)
115 		p->tcol->col = 0;
116 
117 	/* Loop over output lines. */
118 
119 	for (;;) {
120 		vfield = p->tcol->rmargin > p->viscol + vbl ?
121 		    p->tcol->rmargin - p->viscol - vbl : 0;
122 
123 		/*
124 		 * Normally, break the line at the the right margin
125 		 * of the field, but with the NOBREAK flag, only
126 		 * break it at the max right margin of the screen,
127 		 * and with the BRNEVER flag, never break it at all.
128 		 */
129 
130 		vtarget = p->flags & TERMP_BRNEVER ? SIZE_MAX :
131 		    (p->flags & TERMP_NOBREAK) == 0 ? vfield :
132 		    p->maxrmargin > p->viscol + vbl ?
133 		    p->maxrmargin - p->viscol - vbl : 0;
134 
135 		/*
136 		 * Figure out how much text will fit in the field.
137 		 * If there is whitespace only, print nothing.
138 		 */
139 
140 		term_fill(p, &nbr, &vbr, vtarget);
141 		if (nbr == 0)
142 			break;
143 
144 		/*
145 		 * With the CENTER or RIGHT flag, increase the indentation
146 		 * to center the text between the left and right margins
147 		 * or to adjust it to the right margin, respectively.
148 		 */
149 
150 		if (vbr < vtarget) {
151 			if (p->flags & TERMP_CENTER)
152 				vbl += (vtarget - vbr) / 2;
153 			else if (p->flags & TERMP_RIGHT)
154 				vbl += vtarget - vbr;
155 		}
156 
157 		/* Finally, print the field content. */
158 
159 		term_field(p, vbl, nbr, vbr, vtarget);
160 
161 		/*
162 		 * If there is no text left in the field, exit the loop.
163 		 * If the BRTRSP flag is set, consider trailing
164 		 * whitespace significant when deciding whether
165 		 * the field fits or not.
166 		 */
167 
168 		for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
169 			switch (p->tcol->buf[ic]) {
170 			case '\t':
171 				if (p->flags & TERMP_BRTRSP)
172 					vbr = term_tab_next(vbr);
173 				continue;
174 			case ' ':
175 				if (p->flags & TERMP_BRTRSP)
176 					vbr += (*p->width)(p, ' ');
177 				continue;
178 			case '\n':
179 			case ASCII_BREAK:
180 				continue;
181 			default:
182 				break;
183 			}
184 			break;
185 		}
186 		if (ic == p->tcol->lastcol)
187 			break;
188 
189 		/*
190 		 * At the location of an automtic line break, input
191 		 * space characters are consumed by the line break.
192 		 */
193 
194 		while (p->tcol->col < p->tcol->lastcol &&
195 		    p->tcol->buf[p->tcol->col] == ' ')
196 			p->tcol->col++;
197 
198 		/*
199 		 * In multi-column mode, leave the rest of the text
200 		 * in the buffer to be handled by a subsequent
201 		 * invocation, such that the other columns of the
202 		 * table can be handled first.
203 		 * In single-column mode, simply break the line.
204 		 */
205 
206 		if (p->flags & TERMP_MULTICOL)
207 			return;
208 
209 		endline(p);
210 		p->viscol = 0;
211 
212 		/*
213 		 * Normally, start the next line at the same indentation
214 		 * as this one, but with the BRIND flag, start it at the
215 		 * right margin instead.  This is used together with
216 		 * NOBREAK for the tags in various kinds of tagged lists.
217 		 */
218 
219 		vbl = p->flags & TERMP_BRIND ?
220 		    p->tcol->rmargin : p->tcol->offset;
221 	}
222 
223 	/* Reset output state in preparation for the next field. */
224 
225 	p->col = p->tcol->col = p->tcol->lastcol = 0;
226 	p->minbl = p->trailspace;
227 	p->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE | TERMP_NOPAD);
228 
229 	if (p->flags & TERMP_MULTICOL)
230 		return;
231 
232 	/*
233 	 * The HANG flag means that the next field
234 	 * always follows on the same line.
235 	 * The NOBREAK flag means that the next field
236 	 * follows on the same line unless the field was overrun.
237 	 * Normally, break the line at the end of each field.
238 	 */
239 
240 	if ((p->flags & TERMP_HANG) == 0 &&
241 	    ((p->flags & TERMP_NOBREAK) == 0 ||
242 	     vbr + term_len(p, p->trailspace) > vfield))
243 		endline(p);
244 }
245 
246 /*
247  * Store the number of input characters to print in this field in *nbr
248  * and their total visual width to print in *vbr.
249  * If there is only whitespace in the field, both remain zero.
250  * The desired visual width of the field is provided by vtarget.
251  * If the first word is longer, the field will be overrun.
252  */
253 static void
254 term_fill(struct termp *p, size_t *nbr, size_t *vbr, size_t vtarget)
255 {
256 	size_t	 ic;        /* Character position in the input buffer. */
257 	size_t	 vis;       /* Visual position of the current character. */
258 	size_t	 vn;        /* Visual position of the next character. */
259 	int	 breakline; /* Break at the end of this word. */
260 	int	 graph;     /* Last character was non-blank. */
261 
262 	*nbr = *vbr = vis = 0;
263 	breakline = graph = 0;
264 	for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
265 		switch (p->tcol->buf[ic]) {
266 		case '\b':  /* Escape \o (overstrike) or backspace markup. */
267 			assert(ic > 0);
268 			vis -= (*p->width)(p, p->tcol->buf[ic - 1]);
269 			continue;
270 
271 		case '\t':  /* Normal ASCII whitespace. */
272 		case ' ':
273 		case ASCII_BREAK:  /* Escape \: (breakpoint). */
274 			switch (p->tcol->buf[ic]) {
275 			case '\t':
276 				vn = term_tab_next(vis);
277 				break;
278 			case ' ':
279 				vn = vis + (*p->width)(p, ' ');
280 				break;
281 			case ASCII_BREAK:
282 				vn = vis;
283 				break;
284 			default:
285 				abort();
286 			}
287 			/* Can break at the end of a word. */
288 			if (breakline || vn > vtarget)
289 				break;
290 			if (graph) {
291 				*nbr = ic;
292 				*vbr = vis;
293 				graph = 0;
294 			}
295 			vis = vn;
296 			continue;
297 
298 		case '\n':  /* Escape \p (break at the end of the word). */
299 			breakline = 1;
300 			continue;
301 
302 		case ASCII_HYPH:  /* Breakable hyphen. */
303 			graph = 1;
304 			/*
305 			 * We are about to decide whether to break the
306 			 * line or not, so we no longer need this hyphen
307 			 * to be marked as breakable.  Put back a real
308 			 * hyphen such that we get the correct width.
309 			 */
310 			p->tcol->buf[ic] = '-';
311 			vis += (*p->width)(p, '-');
312 			if (vis > vtarget) {
313 				ic++;
314 				break;
315 			}
316 			*nbr = ic + 1;
317 			*vbr = vis;
318 			continue;
319 
320 		case ASCII_NBRSP:  /* Non-breakable space. */
321 			p->tcol->buf[ic] = ' ';
322 			/* FALLTHROUGH */
323 		default:  /* Printable character. */
324 			graph = 1;
325 			vis += (*p->width)(p, p->tcol->buf[ic]);
326 			if (vis > vtarget && *nbr > 0)
327 				return;
328 			continue;
329 		}
330 		break;
331 	}
332 
333 	/*
334 	 * If the last word extends to the end of the field without any
335 	 * trailing whitespace, the loop could not check yet whether it
336 	 * can remain on this line.  So do the check now.
337 	 */
338 
339 	if (graph && (vis <= vtarget || *nbr == 0)) {
340 		*nbr = ic;
341 		*vbr = vis;
342 	}
343 }
344 
345 /*
346  * Print the contents of one field
347  * with an indentation of	 vbl	  visual columns,
348  * an input string length of	 nbr	  characters,
349  * an output width of		 vbr	  visual columns,
350  * and a desired field width of	 vtarget  visual columns.
351  */
352 static void
353 term_field(struct termp *p, size_t vbl, size_t nbr, size_t vbr, size_t vtarget)
354 {
355 	size_t	 ic;	/* Character position in the input buffer. */
356 	size_t	 vis;	/* Visual position of the current character. */
357 	size_t	 dv;	/* Visual width of the current character. */
358 	size_t	 vn;	/* Visual position of the next character. */
359 
360 	vis = 0;
361 	for (ic = p->tcol->col; ic < nbr; ic++) {
362 
363 		/*
364 		 * To avoid the printing of trailing whitespace,
365 		 * do not print whitespace right away, only count it.
366 		 */
367 
368 		switch (p->tcol->buf[ic]) {
369 		case '\n':
370 		case ASCII_BREAK:
371 			continue;
372 		case '\t':
373 			vn = term_tab_next(vis);
374 			vbl += vn - vis;
375 			vis = vn;
376 			continue;
377 		case ' ':
378 		case ASCII_NBRSP:
379 			dv = (*p->width)(p, ' ');
380 			vbl += dv;
381 			vis += dv;
382 			continue;
383 		default:
384 			break;
385 		}
386 
387 		/*
388 		 * We found a non-blank character to print,
389 		 * so write preceding white space now.
390 		 */
391 
392 		if (vbl > 0) {
393 			(*p->advance)(p, vbl);
394 			p->viscol += vbl;
395 			vbl = 0;
396 		}
397 
398 		/* Print the character and adjust the visual position. */
399 
400 		(*p->letter)(p, p->tcol->buf[ic]);
401 		if (p->tcol->buf[ic] == '\b') {
402 			dv = (*p->width)(p, p->tcol->buf[ic - 1]);
403 			p->viscol -= dv;
404 			vis -= dv;
405 		} else {
406 			dv = (*p->width)(p, p->tcol->buf[ic]);
407 			p->viscol += dv;
408 			vis += dv;
409 		}
410 	}
411 	p->tcol->col = nbr;
412 }
413 
414 static void
415 endline(struct termp *p)
416 {
417 	if ((p->flags & (TERMP_NEWMC | TERMP_ENDMC)) == TERMP_ENDMC) {
418 		p->mc = NULL;
419 		p->flags &= ~TERMP_ENDMC;
420 	}
421 	if (p->mc != NULL) {
422 		if (p->viscol && p->maxrmargin >= p->viscol)
423 			(*p->advance)(p, p->maxrmargin - p->viscol + 1);
424 		p->flags |= TERMP_NOBUF | TERMP_NOSPACE;
425 		term_word(p, p->mc);
426 		p->flags &= ~(TERMP_NOBUF | TERMP_NEWMC);
427 	}
428 	p->viscol = 0;
429 	p->minbl = 0;
430 	(*p->endline)(p);
431 }
432 
433 /*
434  * A newline only breaks an existing line; it won't assert vertical
435  * space.  All data in the output buffer is flushed prior to the newline
436  * assertion.
437  */
438 void
439 term_newln(struct termp *p)
440 {
441 
442 	p->flags |= TERMP_NOSPACE;
443 	if (p->tcol->lastcol || p->viscol)
444 		term_flushln(p);
445 }
446 
447 /*
448  * Asserts a vertical space (a full, empty line-break between lines).
449  * Note that if used twice, this will cause two blank spaces and so on.
450  * All data in the output buffer is flushed prior to the newline
451  * assertion.
452  */
453 void
454 term_vspace(struct termp *p)
455 {
456 
457 	term_newln(p);
458 	p->viscol = 0;
459 	p->minbl = 0;
460 	if (0 < p->skipvsp)
461 		p->skipvsp--;
462 	else
463 		(*p->endline)(p);
464 }
465 
466 /* Swap current and previous font; for \fP and .ft P */
467 void
468 term_fontlast(struct termp *p)
469 {
470 	enum termfont	 f;
471 
472 	f = p->fontl;
473 	p->fontl = p->fontq[p->fonti];
474 	p->fontq[p->fonti] = f;
475 }
476 
477 /* Set font, save current, discard previous; for \f, .ft, .B etc. */
478 void
479 term_fontrepl(struct termp *p, enum termfont f)
480 {
481 
482 	p->fontl = p->fontq[p->fonti];
483 	p->fontq[p->fonti] = f;
484 }
485 
486 /* Set font, save previous. */
487 void
488 term_fontpush(struct termp *p, enum termfont f)
489 {
490 
491 	p->fontl = p->fontq[p->fonti];
492 	if (++p->fonti == p->fontsz) {
493 		p->fontsz += 8;
494 		p->fontq = mandoc_reallocarray(p->fontq,
495 		    p->fontsz, sizeof(*p->fontq));
496 	}
497 	p->fontq[p->fonti] = f;
498 }
499 
500 /* Flush to make the saved pointer current again. */
501 void
502 term_fontpopq(struct termp *p, int i)
503 {
504 
505 	assert(i >= 0);
506 	if (p->fonti > i)
507 		p->fonti = i;
508 }
509 
510 /* Pop one font off the stack. */
511 void
512 term_fontpop(struct termp *p)
513 {
514 
515 	assert(p->fonti);
516 	p->fonti--;
517 }
518 
519 /*
520  * Handle pwords, partial words, which may be either a single word or a
521  * phrase that cannot be broken down (such as a literal string).  This
522  * handles word styling.
523  */
524 void
525 term_word(struct termp *p, const char *word)
526 {
527 	struct roffsu	 su;
528 	const char	 nbrsp[2] = { ASCII_NBRSP, 0 };
529 	const char	*seq, *cp;
530 	int		 sz, uc;
531 	size_t		 csz, lsz, ssz;
532 	enum mandoc_esc	 esc;
533 
534 	if ((p->flags & TERMP_NOBUF) == 0) {
535 		if ((p->flags & TERMP_NOSPACE) == 0) {
536 			if ((p->flags & TERMP_KEEP) == 0) {
537 				bufferc(p, ' ');
538 				if (p->flags & TERMP_SENTENCE)
539 					bufferc(p, ' ');
540 			} else
541 				bufferc(p, ASCII_NBRSP);
542 		}
543 		if (p->flags & TERMP_PREKEEP)
544 			p->flags |= TERMP_KEEP;
545 		if (p->flags & TERMP_NONOSPACE)
546 			p->flags |= TERMP_NOSPACE;
547 		else
548 			p->flags &= ~TERMP_NOSPACE;
549 		p->flags &= ~(TERMP_SENTENCE | TERMP_NONEWLINE);
550 		p->skipvsp = 0;
551 	}
552 
553 	while ('\0' != *word) {
554 		if ('\\' != *word) {
555 			if (TERMP_NBRWORD & p->flags) {
556 				if (' ' == *word) {
557 					encode(p, nbrsp, 1);
558 					word++;
559 					continue;
560 				}
561 				ssz = strcspn(word, "\\ ");
562 			} else
563 				ssz = strcspn(word, "\\");
564 			encode(p, word, ssz);
565 			word += (int)ssz;
566 			continue;
567 		}
568 
569 		word++;
570 		esc = mandoc_escape(&word, &seq, &sz);
571 		switch (esc) {
572 		case ESCAPE_UNICODE:
573 			uc = mchars_num2uc(seq + 1, sz - 1);
574 			break;
575 		case ESCAPE_NUMBERED:
576 			uc = mchars_num2char(seq, sz);
577 			if (uc < 0)
578 				continue;
579 			break;
580 		case ESCAPE_SPECIAL:
581 			if (p->enc == TERMENC_ASCII) {
582 				cp = mchars_spec2str(seq, sz, &ssz);
583 				if (cp != NULL)
584 					encode(p, cp, ssz);
585 			} else {
586 				uc = mchars_spec2cp(seq, sz);
587 				if (uc > 0)
588 					encode1(p, uc);
589 			}
590 			continue;
591 		case ESCAPE_UNDEF:
592 			uc = *seq;
593 			break;
594 		case ESCAPE_FONTBOLD:
595 			term_fontrepl(p, TERMFONT_BOLD);
596 			continue;
597 		case ESCAPE_FONTITALIC:
598 			term_fontrepl(p, TERMFONT_UNDER);
599 			continue;
600 		case ESCAPE_FONTBI:
601 			term_fontrepl(p, TERMFONT_BI);
602 			continue;
603 		case ESCAPE_FONT:
604 		case ESCAPE_FONTCW:
605 		case ESCAPE_FONTROMAN:
606 			term_fontrepl(p, TERMFONT_NONE);
607 			continue;
608 		case ESCAPE_FONTPREV:
609 			term_fontlast(p);
610 			continue;
611 		case ESCAPE_BREAK:
612 			bufferc(p, '\n');
613 			continue;
614 		case ESCAPE_NOSPACE:
615 			if (p->flags & TERMP_BACKAFTER)
616 				p->flags &= ~TERMP_BACKAFTER;
617 			else if (*word == '\0')
618 				p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE);
619 			continue;
620 		case ESCAPE_DEVICE:
621 			if (p->type == TERMTYPE_PDF)
622 				encode(p, "pdf", 3);
623 			else if (p->type == TERMTYPE_PS)
624 				encode(p, "ps", 2);
625 			else if (p->enc == TERMENC_ASCII)
626 				encode(p, "ascii", 5);
627 			else
628 				encode(p, "utf8", 4);
629 			continue;
630 		case ESCAPE_HORIZ:
631 			if (*seq == '|') {
632 				seq++;
633 				uc = -p->col;
634 			} else
635 				uc = 0;
636 			if (a2roffsu(seq, &su, SCALE_EM) == NULL)
637 				continue;
638 			uc += term_hen(p, &su);
639 			if (uc > 0)
640 				while (uc-- > 0)
641 					bufferc(p, ASCII_NBRSP);
642 			else if (p->col > (size_t)(-uc))
643 				p->col += uc;
644 			else {
645 				uc += p->col;
646 				p->col = 0;
647 				if (p->tcol->offset > (size_t)(-uc)) {
648 					p->ti += uc;
649 					p->tcol->offset += uc;
650 				} else {
651 					p->ti -= p->tcol->offset;
652 					p->tcol->offset = 0;
653 				}
654 			}
655 			continue;
656 		case ESCAPE_HLINE:
657 			if ((cp = a2roffsu(seq, &su, SCALE_EM)) == NULL)
658 				continue;
659 			uc = term_hen(p, &su);
660 			if (uc <= 0) {
661 				if (p->tcol->rmargin <= p->tcol->offset)
662 					continue;
663 				lsz = p->tcol->rmargin - p->tcol->offset;
664 			} else
665 				lsz = uc;
666 			if (*cp == seq[-1])
667 				uc = -1;
668 			else if (*cp == '\\') {
669 				seq = cp + 1;
670 				esc = mandoc_escape(&seq, &cp, &sz);
671 				switch (esc) {
672 				case ESCAPE_UNICODE:
673 					uc = mchars_num2uc(cp + 1, sz - 1);
674 					break;
675 				case ESCAPE_NUMBERED:
676 					uc = mchars_num2char(cp, sz);
677 					break;
678 				case ESCAPE_SPECIAL:
679 					uc = mchars_spec2cp(cp, sz);
680 					break;
681 				case ESCAPE_UNDEF:
682 					uc = *seq;
683 					break;
684 				default:
685 					uc = -1;
686 					break;
687 				}
688 			} else
689 				uc = *cp;
690 			if (uc < 0x20 || (uc > 0x7E && uc < 0xA0))
691 				uc = '_';
692 			if (p->enc == TERMENC_ASCII) {
693 				cp = ascii_uc2str(uc);
694 				csz = term_strlen(p, cp);
695 				ssz = strlen(cp);
696 			} else
697 				csz = (*p->width)(p, uc);
698 			while (lsz >= csz) {
699 				if (p->enc == TERMENC_ASCII)
700 					encode(p, cp, ssz);
701 				else
702 					encode1(p, uc);
703 				lsz -= csz;
704 			}
705 			continue;
706 		case ESCAPE_SKIPCHAR:
707 			p->flags |= TERMP_BACKAFTER;
708 			continue;
709 		case ESCAPE_OVERSTRIKE:
710 			cp = seq + sz;
711 			while (seq < cp) {
712 				if (*seq == '\\') {
713 					mandoc_escape(&seq, NULL, NULL);
714 					continue;
715 				}
716 				encode1(p, *seq++);
717 				if (seq < cp) {
718 					if (p->flags & TERMP_BACKBEFORE)
719 						p->flags |= TERMP_BACKAFTER;
720 					else
721 						p->flags |= TERMP_BACKBEFORE;
722 				}
723 			}
724 			/* Trim trailing backspace/blank pair. */
725 			if (p->tcol->lastcol > 2 &&
726 			    (p->tcol->buf[p->tcol->lastcol - 1] == ' ' ||
727 			     p->tcol->buf[p->tcol->lastcol - 1] == '\t'))
728 				p->tcol->lastcol -= 2;
729 			if (p->col > p->tcol->lastcol)
730 				p->col = p->tcol->lastcol;
731 			continue;
732 		default:
733 			continue;
734 		}
735 
736 		/*
737 		 * Common handling for Unicode and numbered
738 		 * character escape sequences.
739 		 */
740 
741 		if (p->enc == TERMENC_ASCII) {
742 			cp = ascii_uc2str(uc);
743 			encode(p, cp, strlen(cp));
744 		} else {
745 			if ((uc < 0x20 && uc != 0x09) ||
746 			    (uc > 0x7E && uc < 0xA0))
747 				uc = 0xFFFD;
748 			encode1(p, uc);
749 		}
750 	}
751 	p->flags &= ~TERMP_NBRWORD;
752 }
753 
754 static void
755 adjbuf(struct termp_col *c, size_t sz)
756 {
757 	if (c->maxcols == 0)
758 		c->maxcols = 1024;
759 	while (c->maxcols <= sz)
760 		c->maxcols <<= 2;
761 	c->buf = mandoc_reallocarray(c->buf, c->maxcols, sizeof(*c->buf));
762 }
763 
764 static void
765 bufferc(struct termp *p, char c)
766 {
767 	if (p->flags & TERMP_NOBUF) {
768 		(*p->letter)(p, c);
769 		return;
770 	}
771 	if (p->col + 1 >= p->tcol->maxcols)
772 		adjbuf(p->tcol, p->col + 1);
773 	if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
774 		p->tcol->buf[p->col] = c;
775 	if (p->tcol->lastcol < ++p->col)
776 		p->tcol->lastcol = p->col;
777 }
778 
779 /*
780  * See encode().
781  * Do this for a single (probably unicode) value.
782  * Does not check for non-decorated glyphs.
783  */
784 static void
785 encode1(struct termp *p, int c)
786 {
787 	enum termfont	  f;
788 
789 	if (p->flags & TERMP_NOBUF) {
790 		(*p->letter)(p, c);
791 		return;
792 	}
793 
794 	if (p->col + 7 >= p->tcol->maxcols)
795 		adjbuf(p->tcol, p->col + 7);
796 
797 	f = (c == ASCII_HYPH || c > 127 || isgraph(c)) ?
798 	    p->fontq[p->fonti] : TERMFONT_NONE;
799 
800 	if (p->flags & TERMP_BACKBEFORE) {
801 		if (p->tcol->buf[p->col - 1] == ' ' ||
802 		    p->tcol->buf[p->col - 1] == '\t')
803 			p->col--;
804 		else
805 			p->tcol->buf[p->col++] = '\b';
806 		p->flags &= ~TERMP_BACKBEFORE;
807 	}
808 	if (f == TERMFONT_UNDER || f == TERMFONT_BI) {
809 		p->tcol->buf[p->col++] = '_';
810 		p->tcol->buf[p->col++] = '\b';
811 	}
812 	if (f == TERMFONT_BOLD || f == TERMFONT_BI) {
813 		if (c == ASCII_HYPH)
814 			p->tcol->buf[p->col++] = '-';
815 		else
816 			p->tcol->buf[p->col++] = c;
817 		p->tcol->buf[p->col++] = '\b';
818 	}
819 	if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
820 		p->tcol->buf[p->col] = c;
821 	if (p->tcol->lastcol < ++p->col)
822 		p->tcol->lastcol = p->col;
823 	if (p->flags & TERMP_BACKAFTER) {
824 		p->flags |= TERMP_BACKBEFORE;
825 		p->flags &= ~TERMP_BACKAFTER;
826 	}
827 }
828 
829 static void
830 encode(struct termp *p, const char *word, size_t sz)
831 {
832 	size_t		  i;
833 
834 	if (p->flags & TERMP_NOBUF) {
835 		for (i = 0; i < sz; i++)
836 			(*p->letter)(p, word[i]);
837 		return;
838 	}
839 
840 	if (p->col + 2 + (sz * 5) >= p->tcol->maxcols)
841 		adjbuf(p->tcol, p->col + 2 + (sz * 5));
842 
843 	for (i = 0; i < sz; i++) {
844 		if (ASCII_HYPH == word[i] ||
845 		    isgraph((unsigned char)word[i]))
846 			encode1(p, word[i]);
847 		else {
848 			if (p->tcol->lastcol <= p->col ||
849 			    (word[i] != ' ' && word[i] != ASCII_NBRSP))
850 				p->tcol->buf[p->col] = word[i];
851 			p->col++;
852 
853 			/*
854 			 * Postpone the effect of \z while handling
855 			 * an overstrike sequence from ascii_uc2str().
856 			 */
857 
858 			if (word[i] == '\b' &&
859 			    (p->flags & TERMP_BACKBEFORE)) {
860 				p->flags &= ~TERMP_BACKBEFORE;
861 				p->flags |= TERMP_BACKAFTER;
862 			}
863 		}
864 	}
865 	if (p->tcol->lastcol < p->col)
866 		p->tcol->lastcol = p->col;
867 }
868 
869 void
870 term_setwidth(struct termp *p, const char *wstr)
871 {
872 	struct roffsu	 su;
873 	int		 iop, width;
874 
875 	iop = 0;
876 	width = 0;
877 	if (NULL != wstr) {
878 		switch (*wstr) {
879 		case '+':
880 			iop = 1;
881 			wstr++;
882 			break;
883 		case '-':
884 			iop = -1;
885 			wstr++;
886 			break;
887 		default:
888 			break;
889 		}
890 		if (a2roffsu(wstr, &su, SCALE_MAX) != NULL)
891 			width = term_hspan(p, &su);
892 		else
893 			iop = 0;
894 	}
895 	(*p->setwidth)(p, iop, width);
896 }
897 
898 size_t
899 term_len(const struct termp *p, size_t sz)
900 {
901 
902 	return (*p->width)(p, ' ') * sz;
903 }
904 
905 static size_t
906 cond_width(const struct termp *p, int c, int *skip)
907 {
908 
909 	if (*skip) {
910 		(*skip) = 0;
911 		return 0;
912 	} else
913 		return (*p->width)(p, c);
914 }
915 
916 size_t
917 term_strlen(const struct termp *p, const char *cp)
918 {
919 	size_t		 sz, rsz, i;
920 	int		 ssz, skip, uc;
921 	const char	*seq, *rhs;
922 	enum mandoc_esc	 esc;
923 	static const char rej[] = { '\\', ASCII_NBRSP, ASCII_HYPH,
924 			ASCII_BREAK, '\0' };
925 
926 	/*
927 	 * Account for escaped sequences within string length
928 	 * calculations.  This follows the logic in term_word() as we
929 	 * must calculate the width of produced strings.
930 	 */
931 
932 	sz = 0;
933 	skip = 0;
934 	while ('\0' != *cp) {
935 		rsz = strcspn(cp, rej);
936 		for (i = 0; i < rsz; i++)
937 			sz += cond_width(p, *cp++, &skip);
938 
939 		switch (*cp) {
940 		case '\\':
941 			cp++;
942 			rhs = NULL;
943 			esc = mandoc_escape(&cp, &seq, &ssz);
944 			switch (esc) {
945 			case ESCAPE_UNICODE:
946 				uc = mchars_num2uc(seq + 1, ssz - 1);
947 				break;
948 			case ESCAPE_NUMBERED:
949 				uc = mchars_num2char(seq, ssz);
950 				if (uc < 0)
951 					continue;
952 				break;
953 			case ESCAPE_SPECIAL:
954 				if (p->enc == TERMENC_ASCII) {
955 					rhs = mchars_spec2str(seq, ssz, &rsz);
956 					if (rhs != NULL)
957 						break;
958 				} else {
959 					uc = mchars_spec2cp(seq, ssz);
960 					if (uc > 0)
961 						sz += cond_width(p, uc, &skip);
962 				}
963 				continue;
964 			case ESCAPE_UNDEF:
965 				uc = *seq;
966 				break;
967 			case ESCAPE_DEVICE:
968 				if (p->type == TERMTYPE_PDF) {
969 					rhs = "pdf";
970 					rsz = 3;
971 				} else if (p->type == TERMTYPE_PS) {
972 					rhs = "ps";
973 					rsz = 2;
974 				} else if (p->enc == TERMENC_ASCII) {
975 					rhs = "ascii";
976 					rsz = 5;
977 				} else {
978 					rhs = "utf8";
979 					rsz = 4;
980 				}
981 				break;
982 			case ESCAPE_SKIPCHAR:
983 				skip = 1;
984 				continue;
985 			case ESCAPE_OVERSTRIKE:
986 				rsz = 0;
987 				rhs = seq + ssz;
988 				while (seq < rhs) {
989 					if (*seq == '\\') {
990 						mandoc_escape(&seq, NULL, NULL);
991 						continue;
992 					}
993 					i = (*p->width)(p, *seq++);
994 					if (rsz < i)
995 						rsz = i;
996 				}
997 				sz += rsz;
998 				continue;
999 			default:
1000 				continue;
1001 			}
1002 
1003 			/*
1004 			 * Common handling for Unicode and numbered
1005 			 * character escape sequences.
1006 			 */
1007 
1008 			if (rhs == NULL) {
1009 				if (p->enc == TERMENC_ASCII) {
1010 					rhs = ascii_uc2str(uc);
1011 					rsz = strlen(rhs);
1012 				} else {
1013 					if ((uc < 0x20 && uc != 0x09) ||
1014 					    (uc > 0x7E && uc < 0xA0))
1015 						uc = 0xFFFD;
1016 					sz += cond_width(p, uc, &skip);
1017 					continue;
1018 				}
1019 			}
1020 
1021 			if (skip) {
1022 				skip = 0;
1023 				break;
1024 			}
1025 
1026 			/*
1027 			 * Common handling for all escape sequences
1028 			 * printing more than one character.
1029 			 */
1030 
1031 			for (i = 0; i < rsz; i++)
1032 				sz += (*p->width)(p, *rhs++);
1033 			break;
1034 		case ASCII_NBRSP:
1035 			sz += cond_width(p, ' ', &skip);
1036 			cp++;
1037 			break;
1038 		case ASCII_HYPH:
1039 			sz += cond_width(p, '-', &skip);
1040 			cp++;
1041 			break;
1042 		default:
1043 			break;
1044 		}
1045 	}
1046 
1047 	return sz;
1048 }
1049 
1050 int
1051 term_vspan(const struct termp *p, const struct roffsu *su)
1052 {
1053 	double		 r;
1054 	int		 ri;
1055 
1056 	switch (su->unit) {
1057 	case SCALE_BU:
1058 		r = su->scale / 40.0;
1059 		break;
1060 	case SCALE_CM:
1061 		r = su->scale * 6.0 / 2.54;
1062 		break;
1063 	case SCALE_FS:
1064 		r = su->scale * 65536.0 / 40.0;
1065 		break;
1066 	case SCALE_IN:
1067 		r = su->scale * 6.0;
1068 		break;
1069 	case SCALE_MM:
1070 		r = su->scale * 0.006;
1071 		break;
1072 	case SCALE_PC:
1073 		r = su->scale;
1074 		break;
1075 	case SCALE_PT:
1076 		r = su->scale / 12.0;
1077 		break;
1078 	case SCALE_EN:
1079 	case SCALE_EM:
1080 		r = su->scale * 0.6;
1081 		break;
1082 	case SCALE_VS:
1083 		r = su->scale;
1084 		break;
1085 	default:
1086 		abort();
1087 	}
1088 	ri = r > 0.0 ? r + 0.4995 : r - 0.4995;
1089 	return ri < 66 ? ri : 1;
1090 }
1091 
1092 /*
1093  * Convert a scaling width to basic units, rounding towards 0.
1094  */
1095 int
1096 term_hspan(const struct termp *p, const struct roffsu *su)
1097 {
1098 
1099 	return (*p->hspan)(p, su);
1100 }
1101 
1102 /*
1103  * Convert a scaling width to basic units, rounding to closest.
1104  */
1105 int
1106 term_hen(const struct termp *p, const struct roffsu *su)
1107 {
1108 	int bu;
1109 
1110 	if ((bu = (*p->hspan)(p, su)) >= 0)
1111 		return (bu + 11) / 24;
1112 	else
1113 		return -((-bu + 11) / 24);
1114 }
1115