xref: /freebsd-src/contrib/mandoc/term.c (revision c1c95add8c80843ba15d784f95c361d795b1f593)
1*c1c95addSBrooks Davis /* $Id: term.c,v 1.291 2023/04/28 19:11:04 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
3*c1c95addSBrooks Davis  * Copyright (c) 2010-2022 Ingo Schwarze <schwarze@openbsd.org>
461d06d6bSBaptiste Daroussin  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
561d06d6bSBaptiste Daroussin  *
661d06d6bSBaptiste Daroussin  * Permission to use, copy, modify, and distribute this software for any
761d06d6bSBaptiste Daroussin  * purpose with or without fee is hereby granted, provided that the above
861d06d6bSBaptiste Daroussin  * copyright notice and this permission notice appear in all copies.
961d06d6bSBaptiste Daroussin  *
1061d06d6bSBaptiste Daroussin  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1161d06d6bSBaptiste Daroussin  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1261d06d6bSBaptiste Daroussin  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1361d06d6bSBaptiste Daroussin  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1461d06d6bSBaptiste Daroussin  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1561d06d6bSBaptiste Daroussin  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1661d06d6bSBaptiste Daroussin  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1761d06d6bSBaptiste Daroussin  */
1861d06d6bSBaptiste Daroussin #include "config.h"
1961d06d6bSBaptiste Daroussin 
2061d06d6bSBaptiste Daroussin #include <sys/types.h>
2161d06d6bSBaptiste Daroussin 
2261d06d6bSBaptiste Daroussin #include <assert.h>
2361d06d6bSBaptiste Daroussin #include <ctype.h>
247295610fSBaptiste Daroussin #include <stdint.h>
2561d06d6bSBaptiste Daroussin #include <stdio.h>
2661d06d6bSBaptiste Daroussin #include <stdlib.h>
2761d06d6bSBaptiste Daroussin #include <string.h>
2861d06d6bSBaptiste Daroussin 
2961d06d6bSBaptiste Daroussin #include "mandoc.h"
3061d06d6bSBaptiste Daroussin #include "mandoc_aux.h"
3161d06d6bSBaptiste Daroussin #include "out.h"
3261d06d6bSBaptiste Daroussin #include "term.h"
3361d06d6bSBaptiste Daroussin #include "main.h"
3461d06d6bSBaptiste Daroussin 
3561d06d6bSBaptiste Daroussin static	size_t		 cond_width(const struct termp *, int, int *);
3661d06d6bSBaptiste Daroussin static	void		 adjbuf(struct termp_col *, size_t);
3761d06d6bSBaptiste Daroussin static	void		 bufferc(struct termp *, char);
3861d06d6bSBaptiste Daroussin static	void		 encode(struct termp *, const char *, size_t);
3961d06d6bSBaptiste Daroussin static	void		 encode1(struct termp *, int);
4061d06d6bSBaptiste Daroussin static	void		 endline(struct termp *);
416d38604fSBaptiste Daroussin static	void		 term_field(struct termp *, size_t, size_t);
427295610fSBaptiste Daroussin static	void		 term_fill(struct termp *, size_t *, size_t *,
437295610fSBaptiste Daroussin 				size_t);
4461d06d6bSBaptiste Daroussin 
4561d06d6bSBaptiste Daroussin 
4661d06d6bSBaptiste Daroussin void
4761d06d6bSBaptiste Daroussin term_setcol(struct termp *p, size_t maxtcol)
4861d06d6bSBaptiste Daroussin {
4961d06d6bSBaptiste Daroussin 	if (maxtcol > p->maxtcol) {
5061d06d6bSBaptiste Daroussin 		p->tcols = mandoc_recallocarray(p->tcols,
5161d06d6bSBaptiste Daroussin 		    p->maxtcol, maxtcol, sizeof(*p->tcols));
5261d06d6bSBaptiste Daroussin 		p->maxtcol = maxtcol;
5361d06d6bSBaptiste Daroussin 	}
5461d06d6bSBaptiste Daroussin 	p->lasttcol = maxtcol - 1;
5561d06d6bSBaptiste Daroussin 	p->tcol = p->tcols;
5661d06d6bSBaptiste Daroussin }
5761d06d6bSBaptiste Daroussin 
5861d06d6bSBaptiste Daroussin void
5961d06d6bSBaptiste Daroussin term_free(struct termp *p)
6061d06d6bSBaptiste Daroussin {
61*c1c95addSBrooks Davis 	term_tab_free();
6261d06d6bSBaptiste Daroussin 	for (p->tcol = p->tcols; p->tcol < p->tcols + p->maxtcol; p->tcol++)
6361d06d6bSBaptiste Daroussin 		free(p->tcol->buf);
6461d06d6bSBaptiste Daroussin 	free(p->tcols);
6561d06d6bSBaptiste Daroussin 	free(p->fontq);
6661d06d6bSBaptiste Daroussin 	free(p);
6761d06d6bSBaptiste Daroussin }
6861d06d6bSBaptiste Daroussin 
6961d06d6bSBaptiste Daroussin void
7061d06d6bSBaptiste Daroussin term_begin(struct termp *p, term_margin head,
7161d06d6bSBaptiste Daroussin 		term_margin foot, const struct roff_meta *arg)
7261d06d6bSBaptiste Daroussin {
7361d06d6bSBaptiste Daroussin 
7461d06d6bSBaptiste Daroussin 	p->headf = head;
7561d06d6bSBaptiste Daroussin 	p->footf = foot;
7661d06d6bSBaptiste Daroussin 	p->argf = arg;
7761d06d6bSBaptiste Daroussin 	(*p->begin)(p);
7861d06d6bSBaptiste Daroussin }
7961d06d6bSBaptiste Daroussin 
8061d06d6bSBaptiste Daroussin void
8161d06d6bSBaptiste Daroussin term_end(struct termp *p)
8261d06d6bSBaptiste Daroussin {
8361d06d6bSBaptiste Daroussin 
8461d06d6bSBaptiste Daroussin 	(*p->end)(p);
8561d06d6bSBaptiste Daroussin }
8661d06d6bSBaptiste Daroussin 
8761d06d6bSBaptiste Daroussin /*
8861d06d6bSBaptiste Daroussin  * Flush a chunk of text.  By default, break the output line each time
8961d06d6bSBaptiste Daroussin  * the right margin is reached, and continue output on the next line
9061d06d6bSBaptiste Daroussin  * at the same offset as the chunk itself.  By default, also break the
917295610fSBaptiste Daroussin  * output line at the end of the chunk.  There are many flags modifying
927295610fSBaptiste Daroussin  * this behaviour, see the comments in the body of the function.
9361d06d6bSBaptiste Daroussin  */
9461d06d6bSBaptiste Daroussin void
9561d06d6bSBaptiste Daroussin term_flushln(struct termp *p)
9661d06d6bSBaptiste Daroussin {
977295610fSBaptiste Daroussin 	size_t	 vbl;      /* Number of blanks to prepend to the output. */
987295610fSBaptiste Daroussin 	size_t	 vbr;      /* Actual visual position of the end of field. */
997295610fSBaptiste Daroussin 	size_t	 vfield;   /* Desired visual field width. */
1007295610fSBaptiste Daroussin 	size_t	 vtarget;  /* Desired visual position of the right margin. */
1017295610fSBaptiste Daroussin 	size_t	 ic;       /* Character position in the input buffer. */
1027295610fSBaptiste Daroussin 	size_t	 nbr;      /* Number of characters to print in this field. */
1037295610fSBaptiste Daroussin 
1047295610fSBaptiste Daroussin 	/*
1057295610fSBaptiste Daroussin 	 * Normally, start writing at the left margin, but with the
1067295610fSBaptiste Daroussin 	 * NOPAD flag, start writing at the current position instead.
1077295610fSBaptiste Daroussin 	 */
10861d06d6bSBaptiste Daroussin 
10961d06d6bSBaptiste Daroussin 	vbl = (p->flags & TERMP_NOPAD) || p->tcol->offset < p->viscol ?
11061d06d6bSBaptiste Daroussin 	    0 : p->tcol->offset - p->viscol;
11161d06d6bSBaptiste Daroussin 	if (p->minbl && vbl < p->minbl)
11261d06d6bSBaptiste Daroussin 		vbl = p->minbl;
11361d06d6bSBaptiste Daroussin 
11461d06d6bSBaptiste Daroussin 	if ((p->flags & TERMP_MULTICOL) == 0)
11561d06d6bSBaptiste Daroussin 		p->tcol->col = 0;
1167295610fSBaptiste Daroussin 
1177295610fSBaptiste Daroussin 	/* Loop over output lines. */
1187295610fSBaptiste Daroussin 
1197295610fSBaptiste Daroussin 	for (;;) {
1207295610fSBaptiste Daroussin 		vfield = p->tcol->rmargin > p->viscol + vbl ?
1217295610fSBaptiste Daroussin 		    p->tcol->rmargin - p->viscol - vbl : 0;
12261d06d6bSBaptiste Daroussin 
12361d06d6bSBaptiste Daroussin 		/*
1247295610fSBaptiste Daroussin 		 * Normally, break the line at the the right margin
1257295610fSBaptiste Daroussin 		 * of the field, but with the NOBREAK flag, only
1267295610fSBaptiste Daroussin 		 * break it at the max right margin of the screen,
1277295610fSBaptiste Daroussin 		 * and with the BRNEVER flag, never break it at all.
12861d06d6bSBaptiste Daroussin 		 */
12961d06d6bSBaptiste Daroussin 
1306d38604fSBaptiste Daroussin 		vtarget = (p->flags & TERMP_NOBREAK) == 0 ? vfield :
1317295610fSBaptiste Daroussin 		    p->maxrmargin > p->viscol + vbl ?
1327295610fSBaptiste Daroussin 		    p->maxrmargin - p->viscol - vbl : 0;
13361d06d6bSBaptiste Daroussin 
13461d06d6bSBaptiste Daroussin 		/*
1357295610fSBaptiste Daroussin 		 * Figure out how much text will fit in the field.
1367295610fSBaptiste Daroussin 		 * If there is whitespace only, print nothing.
13761d06d6bSBaptiste Daroussin 		 */
13861d06d6bSBaptiste Daroussin 
1396d38604fSBaptiste Daroussin 		term_fill(p, &nbr, &vbr,
1406d38604fSBaptiste Daroussin 		    p->flags & TERMP_BRNEVER ? SIZE_MAX : vtarget);
1417295610fSBaptiste Daroussin 		if (nbr == 0)
14261d06d6bSBaptiste Daroussin 			break;
14361d06d6bSBaptiste Daroussin 
1447295610fSBaptiste Daroussin 		/*
1457295610fSBaptiste Daroussin 		 * With the CENTER or RIGHT flag, increase the indentation
1467295610fSBaptiste Daroussin 		 * to center the text between the left and right margins
1477295610fSBaptiste Daroussin 		 * or to adjust it to the right margin, respectively.
1487295610fSBaptiste Daroussin 		 */
1497295610fSBaptiste Daroussin 
1507295610fSBaptiste Daroussin 		if (vbr < vtarget) {
1517295610fSBaptiste Daroussin 			if (p->flags & TERMP_CENTER)
1527295610fSBaptiste Daroussin 				vbl += (vtarget - vbr) / 2;
1537295610fSBaptiste Daroussin 			else if (p->flags & TERMP_RIGHT)
1547295610fSBaptiste Daroussin 				vbl += vtarget - vbr;
1557295610fSBaptiste Daroussin 		}
1567295610fSBaptiste Daroussin 
1577295610fSBaptiste Daroussin 		/* Finally, print the field content. */
1587295610fSBaptiste Daroussin 
1596d38604fSBaptiste Daroussin 		term_field(p, vbl, nbr);
160*c1c95addSBrooks Davis 		if (vbr < vtarget)
161*c1c95addSBrooks Davis 			p->tcol->taboff += vbr;
162*c1c95addSBrooks Davis 		else
163*c1c95addSBrooks Davis 			p->tcol->taboff += vtarget;
164*c1c95addSBrooks Davis 		p->tcol->taboff += (*p->width)(p, ' ');
1657295610fSBaptiste Daroussin 
1667295610fSBaptiste Daroussin 		/*
1677295610fSBaptiste Daroussin 		 * If there is no text left in the field, exit the loop.
1687295610fSBaptiste Daroussin 		 * If the BRTRSP flag is set, consider trailing
1697295610fSBaptiste Daroussin 		 * whitespace significant when deciding whether
1707295610fSBaptiste Daroussin 		 * the field fits or not.
1717295610fSBaptiste Daroussin 		 */
1727295610fSBaptiste Daroussin 
1737295610fSBaptiste Daroussin 		for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
1747295610fSBaptiste Daroussin 			switch (p->tcol->buf[ic]) {
1757295610fSBaptiste Daroussin 			case '\t':
1767295610fSBaptiste Daroussin 				if (p->flags & TERMP_BRTRSP)
1777295610fSBaptiste Daroussin 					vbr = term_tab_next(vbr);
17861d06d6bSBaptiste Daroussin 				continue;
1797295610fSBaptiste Daroussin 			case ' ':
1807295610fSBaptiste Daroussin 				if (p->flags & TERMP_BRTRSP)
1817295610fSBaptiste Daroussin 					vbr += (*p->width)(p, ' ');
18261d06d6bSBaptiste Daroussin 				continue;
1837295610fSBaptiste Daroussin 			case '\n':
184*c1c95addSBrooks Davis 			case ASCII_NBRZW:
1857295610fSBaptiste Daroussin 			case ASCII_BREAK:
186*c1c95addSBrooks Davis 			case ASCII_TABREF:
1877295610fSBaptiste Daroussin 				continue;
1887295610fSBaptiste Daroussin 			default:
18961d06d6bSBaptiste Daroussin 				break;
1907295610fSBaptiste Daroussin 			}
1917295610fSBaptiste Daroussin 			break;
1927295610fSBaptiste Daroussin 		}
1937295610fSBaptiste Daroussin 		if (ic == p->tcol->lastcol)
1947295610fSBaptiste Daroussin 			break;
1957295610fSBaptiste Daroussin 
1967295610fSBaptiste Daroussin 		/*
197*c1c95addSBrooks Davis 		 * At the location of an automatic line break, input
1987295610fSBaptiste Daroussin 		 * space characters are consumed by the line break.
1997295610fSBaptiste Daroussin 		 */
2007295610fSBaptiste Daroussin 
20161d06d6bSBaptiste Daroussin 		while (p->tcol->col < p->tcol->lastcol &&
20261d06d6bSBaptiste Daroussin 		    p->tcol->buf[p->tcol->col] == ' ')
20361d06d6bSBaptiste Daroussin 			p->tcol->col++;
20461d06d6bSBaptiste Daroussin 
20561d06d6bSBaptiste Daroussin 		/*
2067295610fSBaptiste Daroussin 		 * In multi-column mode, leave the rest of the text
2077295610fSBaptiste Daroussin 		 * in the buffer to be handled by a subsequent
2087295610fSBaptiste Daroussin 		 * invocation, such that the other columns of the
2097295610fSBaptiste Daroussin 		 * table can be handled first.
2107295610fSBaptiste Daroussin 		 * In single-column mode, simply break the line.
21161d06d6bSBaptiste Daroussin 		 */
21261d06d6bSBaptiste Daroussin 
21361d06d6bSBaptiste Daroussin 		if (p->flags & TERMP_MULTICOL)
21461d06d6bSBaptiste Daroussin 			return;
21561d06d6bSBaptiste Daroussin 
21661d06d6bSBaptiste Daroussin 		endline(p);
21761d06d6bSBaptiste Daroussin 
21861d06d6bSBaptiste Daroussin 		/*
2197295610fSBaptiste Daroussin 		 * Normally, start the next line at the same indentation
2207295610fSBaptiste Daroussin 		 * as this one, but with the BRIND flag, start it at the
2217295610fSBaptiste Daroussin 		 * right margin instead.  This is used together with
2227295610fSBaptiste Daroussin 		 * NOBREAK for the tags in various kinds of tagged lists.
22361d06d6bSBaptiste Daroussin 		 */
22461d06d6bSBaptiste Daroussin 
2257295610fSBaptiste Daroussin 		vbl = p->flags & TERMP_BRIND ?
2267295610fSBaptiste Daroussin 		    p->tcol->rmargin : p->tcol->offset;
2277295610fSBaptiste Daroussin 	}
2287295610fSBaptiste Daroussin 
2297295610fSBaptiste Daroussin 	/* Reset output state in preparation for the next field. */
23061d06d6bSBaptiste Daroussin 
23161d06d6bSBaptiste Daroussin 	p->col = p->tcol->col = p->tcol->lastcol = 0;
23261d06d6bSBaptiste Daroussin 	p->minbl = p->trailspace;
23361d06d6bSBaptiste Daroussin 	p->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE | TERMP_NOPAD);
23461d06d6bSBaptiste Daroussin 
23561d06d6bSBaptiste Daroussin 	if (p->flags & TERMP_MULTICOL)
23661d06d6bSBaptiste Daroussin 		return;
23761d06d6bSBaptiste Daroussin 
2387295610fSBaptiste Daroussin 	/*
2397295610fSBaptiste Daroussin 	 * The HANG flag means that the next field
2407295610fSBaptiste Daroussin 	 * always follows on the same line.
2417295610fSBaptiste Daroussin 	 * The NOBREAK flag means that the next field
2427295610fSBaptiste Daroussin 	 * follows on the same line unless the field was overrun.
2437295610fSBaptiste Daroussin 	 * Normally, break the line at the end of each field.
2447295610fSBaptiste Daroussin 	 */
24561d06d6bSBaptiste Daroussin 
2467295610fSBaptiste Daroussin 	if ((p->flags & TERMP_HANG) == 0 &&
2477295610fSBaptiste Daroussin 	    ((p->flags & TERMP_NOBREAK) == 0 ||
2487295610fSBaptiste Daroussin 	     vbr + term_len(p, p->trailspace) > vfield))
24961d06d6bSBaptiste Daroussin 		endline(p);
25061d06d6bSBaptiste Daroussin }
25161d06d6bSBaptiste Daroussin 
2527295610fSBaptiste Daroussin /*
2537295610fSBaptiste Daroussin  * Store the number of input characters to print in this field in *nbr
2547295610fSBaptiste Daroussin  * and their total visual width to print in *vbr.
2557295610fSBaptiste Daroussin  * If there is only whitespace in the field, both remain zero.
2567295610fSBaptiste Daroussin  * The desired visual width of the field is provided by vtarget.
2577295610fSBaptiste Daroussin  * If the first word is longer, the field will be overrun.
2587295610fSBaptiste Daroussin  */
2597295610fSBaptiste Daroussin static void
2607295610fSBaptiste Daroussin term_fill(struct termp *p, size_t *nbr, size_t *vbr, size_t vtarget)
2617295610fSBaptiste Daroussin {
2627295610fSBaptiste Daroussin 	size_t	 ic;        /* Character position in the input buffer. */
2637295610fSBaptiste Daroussin 	size_t	 vis;       /* Visual position of the current character. */
2647295610fSBaptiste Daroussin 	size_t	 vn;        /* Visual position of the next character. */
2657295610fSBaptiste Daroussin 	int	 breakline; /* Break at the end of this word. */
2667295610fSBaptiste Daroussin 	int	 graph;     /* Last character was non-blank. */
267*c1c95addSBrooks Davis 	int	 taboff;    /* Temporary offset for literal tabs. */
2687295610fSBaptiste Daroussin 
2697295610fSBaptiste Daroussin 	*nbr = *vbr = vis = 0;
2707295610fSBaptiste Daroussin 	breakline = graph = 0;
271*c1c95addSBrooks Davis 	taboff = p->tcol->taboff;
2727295610fSBaptiste Daroussin 	for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
2737295610fSBaptiste Daroussin 		switch (p->tcol->buf[ic]) {
2747295610fSBaptiste Daroussin 		case '\b':  /* Escape \o (overstrike) or backspace markup. */
2757295610fSBaptiste Daroussin 			assert(ic > 0);
2767295610fSBaptiste Daroussin 			vis -= (*p->width)(p, p->tcol->buf[ic - 1]);
2777295610fSBaptiste Daroussin 			continue;
2787295610fSBaptiste Daroussin 
2797295610fSBaptiste Daroussin 		case ' ':
2807295610fSBaptiste Daroussin 		case ASCII_BREAK:  /* Escape \: (breakpoint). */
2817295610fSBaptiste Daroussin 			vn = vis;
282*c1c95addSBrooks Davis 			if (p->tcol->buf[ic] == ' ')
283*c1c95addSBrooks Davis 				vn += (*p->width)(p, ' ');
2847295610fSBaptiste Daroussin 			/* Can break at the end of a word. */
2857295610fSBaptiste Daroussin 			if (breakline || vn > vtarget)
2867295610fSBaptiste Daroussin 				break;
2877295610fSBaptiste Daroussin 			if (graph) {
2887295610fSBaptiste Daroussin 				*nbr = ic;
2897295610fSBaptiste Daroussin 				*vbr = vis;
2907295610fSBaptiste Daroussin 				graph = 0;
2917295610fSBaptiste Daroussin 			}
2927295610fSBaptiste Daroussin 			vis = vn;
2937295610fSBaptiste Daroussin 			continue;
2947295610fSBaptiste Daroussin 
2957295610fSBaptiste Daroussin 		case '\n':  /* Escape \p (break at the end of the word). */
2967295610fSBaptiste Daroussin 			breakline = 1;
2977295610fSBaptiste Daroussin 			continue;
2987295610fSBaptiste Daroussin 
2997295610fSBaptiste Daroussin 		case ASCII_HYPH:  /* Breakable hyphen. */
3007295610fSBaptiste Daroussin 			graph = 1;
3017295610fSBaptiste Daroussin 			/*
3027295610fSBaptiste Daroussin 			 * We are about to decide whether to break the
3037295610fSBaptiste Daroussin 			 * line or not, so we no longer need this hyphen
3047295610fSBaptiste Daroussin 			 * to be marked as breakable.  Put back a real
3057295610fSBaptiste Daroussin 			 * hyphen such that we get the correct width.
3067295610fSBaptiste Daroussin 			 */
3077295610fSBaptiste Daroussin 			p->tcol->buf[ic] = '-';
3087295610fSBaptiste Daroussin 			vis += (*p->width)(p, '-');
3097295610fSBaptiste Daroussin 			if (vis > vtarget) {
3107295610fSBaptiste Daroussin 				ic++;
3117295610fSBaptiste Daroussin 				break;
3127295610fSBaptiste Daroussin 			}
3137295610fSBaptiste Daroussin 			*nbr = ic + 1;
3147295610fSBaptiste Daroussin 			*vbr = vis;
3157295610fSBaptiste Daroussin 			continue;
3167295610fSBaptiste Daroussin 
317*c1c95addSBrooks Davis 		case ASCII_TABREF:
318*c1c95addSBrooks Davis 			taboff = -vis - (*p->width)(p, ' ');
319*c1c95addSBrooks Davis 			continue;
320*c1c95addSBrooks Davis 
321*c1c95addSBrooks Davis 		default:
322*c1c95addSBrooks Davis 			switch (p->tcol->buf[ic]) {
323*c1c95addSBrooks Davis 			case '\t':
324*c1c95addSBrooks Davis 				if (taboff < 0 && (size_t)-taboff > vis)
325*c1c95addSBrooks Davis 					vis = 0;
326*c1c95addSBrooks Davis 				else
327*c1c95addSBrooks Davis 					vis += taboff;
328*c1c95addSBrooks Davis 				vis = term_tab_next(vis);
329*c1c95addSBrooks Davis 				vis -= taboff;
330*c1c95addSBrooks Davis 				break;
331*c1c95addSBrooks Davis 			case ASCII_NBRZW:  /* Non-breakable zero-width. */
332*c1c95addSBrooks Davis 				break;
3337295610fSBaptiste Daroussin 			case ASCII_NBRSP:  /* Non-breakable space. */
3347295610fSBaptiste Daroussin 				p->tcol->buf[ic] = ' ';
3357295610fSBaptiste Daroussin 				/* FALLTHROUGH */
3367295610fSBaptiste Daroussin 			default:  /* Printable character. */
3377295610fSBaptiste Daroussin 				vis += (*p->width)(p, p->tcol->buf[ic]);
338*c1c95addSBrooks Davis 				break;
339*c1c95addSBrooks Davis 			}
340*c1c95addSBrooks Davis 			graph = 1;
3417295610fSBaptiste Daroussin 			if (vis > vtarget && *nbr > 0)
3427295610fSBaptiste Daroussin 				return;
3437295610fSBaptiste Daroussin 			continue;
3447295610fSBaptiste Daroussin 		}
3457295610fSBaptiste Daroussin 		break;
3467295610fSBaptiste Daroussin 	}
3477295610fSBaptiste Daroussin 
3487295610fSBaptiste Daroussin 	/*
3497295610fSBaptiste Daroussin 	 * If the last word extends to the end of the field without any
3507295610fSBaptiste Daroussin 	 * trailing whitespace, the loop could not check yet whether it
3517295610fSBaptiste Daroussin 	 * can remain on this line.  So do the check now.
3527295610fSBaptiste Daroussin 	 */
3537295610fSBaptiste Daroussin 
3547295610fSBaptiste Daroussin 	if (graph && (vis <= vtarget || *nbr == 0)) {
3557295610fSBaptiste Daroussin 		*nbr = ic;
3567295610fSBaptiste Daroussin 		*vbr = vis;
3577295610fSBaptiste Daroussin 	}
3587295610fSBaptiste Daroussin }
3597295610fSBaptiste Daroussin 
3607295610fSBaptiste Daroussin /*
3617295610fSBaptiste Daroussin  * Print the contents of one field
3627295610fSBaptiste Daroussin  * with an indentation of	 vbl	  visual columns,
3636d38604fSBaptiste Daroussin  * and an input string length of nbr	  characters.
3647295610fSBaptiste Daroussin  */
3657295610fSBaptiste Daroussin static void
3666d38604fSBaptiste Daroussin term_field(struct termp *p, size_t vbl, size_t nbr)
3677295610fSBaptiste Daroussin {
3687295610fSBaptiste Daroussin 	size_t	 ic;	/* Character position in the input buffer. */
3697295610fSBaptiste Daroussin 	size_t	 vis;	/* Visual position of the current character. */
370*c1c95addSBrooks Davis 	size_t	 vt;	/* Visual position including tab offset. */
3717295610fSBaptiste Daroussin 	size_t	 dv;	/* Visual width of the current character. */
372*c1c95addSBrooks Davis 	int	 taboff; /* Temporary offset for literal tabs. */
3737295610fSBaptiste Daroussin 
3747295610fSBaptiste Daroussin 	vis = 0;
375*c1c95addSBrooks Davis 	taboff = p->tcol->taboff;
3767295610fSBaptiste Daroussin 	for (ic = p->tcol->col; ic < nbr; ic++) {
3777295610fSBaptiste Daroussin 
3787295610fSBaptiste Daroussin 		/*
3797295610fSBaptiste Daroussin 		 * To avoid the printing of trailing whitespace,
3807295610fSBaptiste Daroussin 		 * do not print whitespace right away, only count it.
3817295610fSBaptiste Daroussin 		 */
3827295610fSBaptiste Daroussin 
3837295610fSBaptiste Daroussin 		switch (p->tcol->buf[ic]) {
3847295610fSBaptiste Daroussin 		case '\n':
3857295610fSBaptiste Daroussin 		case ASCII_BREAK:
386*c1c95addSBrooks Davis 		case ASCII_NBRZW:
387*c1c95addSBrooks Davis 			continue;
388*c1c95addSBrooks Davis 		case ASCII_TABREF:
389*c1c95addSBrooks Davis 			taboff = -vis - (*p->width)(p, ' ');
3907295610fSBaptiste Daroussin 			continue;
3917295610fSBaptiste Daroussin 		case '\t':
3927295610fSBaptiste Daroussin 		case ' ':
3937295610fSBaptiste Daroussin 		case ASCII_NBRSP:
394*c1c95addSBrooks Davis 			if (p->tcol->buf[ic] == '\t') {
395*c1c95addSBrooks Davis 				if (taboff < 0 && (size_t)-taboff > vis)
396*c1c95addSBrooks Davis 					vt = 0;
397*c1c95addSBrooks Davis 				else
398*c1c95addSBrooks Davis 					vt = vis + taboff;
399*c1c95addSBrooks Davis 				dv = term_tab_next(vt) - vt;
400*c1c95addSBrooks Davis 			} else
4017295610fSBaptiste Daroussin 				dv = (*p->width)(p, ' ');
4027295610fSBaptiste Daroussin 			vbl += dv;
4037295610fSBaptiste Daroussin 			vis += dv;
4047295610fSBaptiste Daroussin 			continue;
4057295610fSBaptiste Daroussin 		default:
4067295610fSBaptiste Daroussin 			break;
4077295610fSBaptiste Daroussin 		}
4087295610fSBaptiste Daroussin 
4097295610fSBaptiste Daroussin 		/*
4107295610fSBaptiste Daroussin 		 * We found a non-blank character to print,
4117295610fSBaptiste Daroussin 		 * so write preceding white space now.
4127295610fSBaptiste Daroussin 		 */
4137295610fSBaptiste Daroussin 
4147295610fSBaptiste Daroussin 		if (vbl > 0) {
4157295610fSBaptiste Daroussin 			(*p->advance)(p, vbl);
4167295610fSBaptiste Daroussin 			p->viscol += vbl;
4177295610fSBaptiste Daroussin 			vbl = 0;
4187295610fSBaptiste Daroussin 		}
4197295610fSBaptiste Daroussin 
4207295610fSBaptiste Daroussin 		/* Print the character and adjust the visual position. */
4217295610fSBaptiste Daroussin 
4227295610fSBaptiste Daroussin 		(*p->letter)(p, p->tcol->buf[ic]);
4237295610fSBaptiste Daroussin 		if (p->tcol->buf[ic] == '\b') {
4247295610fSBaptiste Daroussin 			dv = (*p->width)(p, p->tcol->buf[ic - 1]);
4257295610fSBaptiste Daroussin 			p->viscol -= dv;
4267295610fSBaptiste Daroussin 			vis -= dv;
4277295610fSBaptiste Daroussin 		} else {
4287295610fSBaptiste Daroussin 			dv = (*p->width)(p, p->tcol->buf[ic]);
4297295610fSBaptiste Daroussin 			p->viscol += dv;
4307295610fSBaptiste Daroussin 			vis += dv;
4317295610fSBaptiste Daroussin 		}
4327295610fSBaptiste Daroussin 	}
4337295610fSBaptiste Daroussin 	p->tcol->col = nbr;
4347295610fSBaptiste Daroussin }
4357295610fSBaptiste Daroussin 
43661d06d6bSBaptiste Daroussin static void
43761d06d6bSBaptiste Daroussin endline(struct termp *p)
43861d06d6bSBaptiste Daroussin {
43961d06d6bSBaptiste Daroussin 	if ((p->flags & (TERMP_NEWMC | TERMP_ENDMC)) == TERMP_ENDMC) {
44061d06d6bSBaptiste Daroussin 		p->mc = NULL;
44161d06d6bSBaptiste Daroussin 		p->flags &= ~TERMP_ENDMC;
44261d06d6bSBaptiste Daroussin 	}
44361d06d6bSBaptiste Daroussin 	if (p->mc != NULL) {
44461d06d6bSBaptiste Daroussin 		if (p->viscol && p->maxrmargin >= p->viscol)
44561d06d6bSBaptiste Daroussin 			(*p->advance)(p, p->maxrmargin - p->viscol + 1);
44661d06d6bSBaptiste Daroussin 		p->flags |= TERMP_NOBUF | TERMP_NOSPACE;
44761d06d6bSBaptiste Daroussin 		term_word(p, p->mc);
44861d06d6bSBaptiste Daroussin 		p->flags &= ~(TERMP_NOBUF | TERMP_NEWMC);
44961d06d6bSBaptiste Daroussin 	}
45061d06d6bSBaptiste Daroussin 	p->viscol = 0;
45161d06d6bSBaptiste Daroussin 	p->minbl = 0;
45261d06d6bSBaptiste Daroussin 	(*p->endline)(p);
45361d06d6bSBaptiste Daroussin }
45461d06d6bSBaptiste Daroussin 
45561d06d6bSBaptiste Daroussin /*
45661d06d6bSBaptiste Daroussin  * A newline only breaks an existing line; it won't assert vertical
45761d06d6bSBaptiste Daroussin  * space.  All data in the output buffer is flushed prior to the newline
45861d06d6bSBaptiste Daroussin  * assertion.
45961d06d6bSBaptiste Daroussin  */
46061d06d6bSBaptiste Daroussin void
46161d06d6bSBaptiste Daroussin term_newln(struct termp *p)
46261d06d6bSBaptiste Daroussin {
46361d06d6bSBaptiste Daroussin 	p->flags |= TERMP_NOSPACE;
46461d06d6bSBaptiste Daroussin 	if (p->tcol->lastcol || p->viscol)
46561d06d6bSBaptiste Daroussin 		term_flushln(p);
466*c1c95addSBrooks Davis 	p->tcol->taboff = 0;
46761d06d6bSBaptiste Daroussin }
46861d06d6bSBaptiste Daroussin 
46961d06d6bSBaptiste Daroussin /*
47061d06d6bSBaptiste Daroussin  * Asserts a vertical space (a full, empty line-break between lines).
47161d06d6bSBaptiste Daroussin  * Note that if used twice, this will cause two blank spaces and so on.
47261d06d6bSBaptiste Daroussin  * All data in the output buffer is flushed prior to the newline
47361d06d6bSBaptiste Daroussin  * assertion.
47461d06d6bSBaptiste Daroussin  */
47561d06d6bSBaptiste Daroussin void
47661d06d6bSBaptiste Daroussin term_vspace(struct termp *p)
47761d06d6bSBaptiste Daroussin {
47861d06d6bSBaptiste Daroussin 
47961d06d6bSBaptiste Daroussin 	term_newln(p);
48061d06d6bSBaptiste Daroussin 	p->viscol = 0;
48161d06d6bSBaptiste Daroussin 	p->minbl = 0;
48261d06d6bSBaptiste Daroussin 	if (0 < p->skipvsp)
48361d06d6bSBaptiste Daroussin 		p->skipvsp--;
48461d06d6bSBaptiste Daroussin 	else
48561d06d6bSBaptiste Daroussin 		(*p->endline)(p);
48661d06d6bSBaptiste Daroussin }
48761d06d6bSBaptiste Daroussin 
48861d06d6bSBaptiste Daroussin /* Swap current and previous font; for \fP and .ft P */
48961d06d6bSBaptiste Daroussin void
49061d06d6bSBaptiste Daroussin term_fontlast(struct termp *p)
49161d06d6bSBaptiste Daroussin {
49261d06d6bSBaptiste Daroussin 	enum termfont	 f;
49361d06d6bSBaptiste Daroussin 
49461d06d6bSBaptiste Daroussin 	f = p->fontl;
49561d06d6bSBaptiste Daroussin 	p->fontl = p->fontq[p->fonti];
49661d06d6bSBaptiste Daroussin 	p->fontq[p->fonti] = f;
49761d06d6bSBaptiste Daroussin }
49861d06d6bSBaptiste Daroussin 
49961d06d6bSBaptiste Daroussin /* Set font, save current, discard previous; for \f, .ft, .B etc. */
50061d06d6bSBaptiste Daroussin void
50161d06d6bSBaptiste Daroussin term_fontrepl(struct termp *p, enum termfont f)
50261d06d6bSBaptiste Daroussin {
50361d06d6bSBaptiste Daroussin 
50461d06d6bSBaptiste Daroussin 	p->fontl = p->fontq[p->fonti];
50561d06d6bSBaptiste Daroussin 	p->fontq[p->fonti] = f;
50661d06d6bSBaptiste Daroussin }
50761d06d6bSBaptiste Daroussin 
50861d06d6bSBaptiste Daroussin /* Set font, save previous. */
50961d06d6bSBaptiste Daroussin void
51061d06d6bSBaptiste Daroussin term_fontpush(struct termp *p, enum termfont f)
51161d06d6bSBaptiste Daroussin {
51261d06d6bSBaptiste Daroussin 
51361d06d6bSBaptiste Daroussin 	p->fontl = p->fontq[p->fonti];
51461d06d6bSBaptiste Daroussin 	if (++p->fonti == p->fontsz) {
51561d06d6bSBaptiste Daroussin 		p->fontsz += 8;
51661d06d6bSBaptiste Daroussin 		p->fontq = mandoc_reallocarray(p->fontq,
51761d06d6bSBaptiste Daroussin 		    p->fontsz, sizeof(*p->fontq));
51861d06d6bSBaptiste Daroussin 	}
51961d06d6bSBaptiste Daroussin 	p->fontq[p->fonti] = f;
52061d06d6bSBaptiste Daroussin }
52161d06d6bSBaptiste Daroussin 
52261d06d6bSBaptiste Daroussin /* Flush to make the saved pointer current again. */
52361d06d6bSBaptiste Daroussin void
52461d06d6bSBaptiste Daroussin term_fontpopq(struct termp *p, int i)
52561d06d6bSBaptiste Daroussin {
52661d06d6bSBaptiste Daroussin 
52761d06d6bSBaptiste Daroussin 	assert(i >= 0);
52861d06d6bSBaptiste Daroussin 	if (p->fonti > i)
52961d06d6bSBaptiste Daroussin 		p->fonti = i;
53061d06d6bSBaptiste Daroussin }
53161d06d6bSBaptiste Daroussin 
53261d06d6bSBaptiste Daroussin /* Pop one font off the stack. */
53361d06d6bSBaptiste Daroussin void
53461d06d6bSBaptiste Daroussin term_fontpop(struct termp *p)
53561d06d6bSBaptiste Daroussin {
53661d06d6bSBaptiste Daroussin 
53761d06d6bSBaptiste Daroussin 	assert(p->fonti);
53861d06d6bSBaptiste Daroussin 	p->fonti--;
53961d06d6bSBaptiste Daroussin }
54061d06d6bSBaptiste Daroussin 
54161d06d6bSBaptiste Daroussin /*
54261d06d6bSBaptiste Daroussin  * Handle pwords, partial words, which may be either a single word or a
54361d06d6bSBaptiste Daroussin  * phrase that cannot be broken down (such as a literal string).  This
54461d06d6bSBaptiste Daroussin  * handles word styling.
54561d06d6bSBaptiste Daroussin  */
54661d06d6bSBaptiste Daroussin void
54761d06d6bSBaptiste Daroussin term_word(struct termp *p, const char *word)
54861d06d6bSBaptiste Daroussin {
54961d06d6bSBaptiste Daroussin 	struct roffsu	 su;
55061d06d6bSBaptiste Daroussin 	const char	 nbrsp[2] = { ASCII_NBRSP, 0 };
55161d06d6bSBaptiste Daroussin 	const char	*seq, *cp;
55261d06d6bSBaptiste Daroussin 	int		 sz, uc;
55361d06d6bSBaptiste Daroussin 	size_t		 csz, lsz, ssz;
55461d06d6bSBaptiste Daroussin 	enum mandoc_esc	 esc;
55561d06d6bSBaptiste Daroussin 
55661d06d6bSBaptiste Daroussin 	if ((p->flags & TERMP_NOBUF) == 0) {
55761d06d6bSBaptiste Daroussin 		if ((p->flags & TERMP_NOSPACE) == 0) {
55861d06d6bSBaptiste Daroussin 			if ((p->flags & TERMP_KEEP) == 0) {
55961d06d6bSBaptiste Daroussin 				bufferc(p, ' ');
56061d06d6bSBaptiste Daroussin 				if (p->flags & TERMP_SENTENCE)
56161d06d6bSBaptiste Daroussin 					bufferc(p, ' ');
56261d06d6bSBaptiste Daroussin 			} else
56361d06d6bSBaptiste Daroussin 				bufferc(p, ASCII_NBRSP);
56461d06d6bSBaptiste Daroussin 		}
56561d06d6bSBaptiste Daroussin 		if (p->flags & TERMP_PREKEEP)
56661d06d6bSBaptiste Daroussin 			p->flags |= TERMP_KEEP;
56761d06d6bSBaptiste Daroussin 		if (p->flags & TERMP_NONOSPACE)
56861d06d6bSBaptiste Daroussin 			p->flags |= TERMP_NOSPACE;
56961d06d6bSBaptiste Daroussin 		else
57061d06d6bSBaptiste Daroussin 			p->flags &= ~TERMP_NOSPACE;
57161d06d6bSBaptiste Daroussin 		p->flags &= ~(TERMP_SENTENCE | TERMP_NONEWLINE);
57261d06d6bSBaptiste Daroussin 		p->skipvsp = 0;
57361d06d6bSBaptiste Daroussin 	}
57461d06d6bSBaptiste Daroussin 
57561d06d6bSBaptiste Daroussin 	while ('\0' != *word) {
57661d06d6bSBaptiste Daroussin 		if ('\\' != *word) {
57761d06d6bSBaptiste Daroussin 			if (TERMP_NBRWORD & p->flags) {
57861d06d6bSBaptiste Daroussin 				if (' ' == *word) {
57961d06d6bSBaptiste Daroussin 					encode(p, nbrsp, 1);
58061d06d6bSBaptiste Daroussin 					word++;
58161d06d6bSBaptiste Daroussin 					continue;
58261d06d6bSBaptiste Daroussin 				}
58361d06d6bSBaptiste Daroussin 				ssz = strcspn(word, "\\ ");
58461d06d6bSBaptiste Daroussin 			} else
58561d06d6bSBaptiste Daroussin 				ssz = strcspn(word, "\\");
58661d06d6bSBaptiste Daroussin 			encode(p, word, ssz);
58761d06d6bSBaptiste Daroussin 			word += (int)ssz;
58861d06d6bSBaptiste Daroussin 			continue;
58961d06d6bSBaptiste Daroussin 		}
59061d06d6bSBaptiste Daroussin 
59161d06d6bSBaptiste Daroussin 		word++;
59261d06d6bSBaptiste Daroussin 		esc = mandoc_escape(&word, &seq, &sz);
59361d06d6bSBaptiste Daroussin 		switch (esc) {
59461d06d6bSBaptiste Daroussin 		case ESCAPE_UNICODE:
59561d06d6bSBaptiste Daroussin 			uc = mchars_num2uc(seq + 1, sz - 1);
59661d06d6bSBaptiste Daroussin 			break;
59761d06d6bSBaptiste Daroussin 		case ESCAPE_NUMBERED:
59861d06d6bSBaptiste Daroussin 			uc = mchars_num2char(seq, sz);
599*c1c95addSBrooks Davis 			if (uc >= 0)
60061d06d6bSBaptiste Daroussin 				break;
601*c1c95addSBrooks Davis 			bufferc(p, ASCII_NBRZW);
602*c1c95addSBrooks Davis 			continue;
60361d06d6bSBaptiste Daroussin 		case ESCAPE_SPECIAL:
60461d06d6bSBaptiste Daroussin 			if (p->enc == TERMENC_ASCII) {
60561d06d6bSBaptiste Daroussin 				cp = mchars_spec2str(seq, sz, &ssz);
60661d06d6bSBaptiste Daroussin 				if (cp != NULL)
60761d06d6bSBaptiste Daroussin 					encode(p, cp, ssz);
608*c1c95addSBrooks Davis 				else
609*c1c95addSBrooks Davis 					bufferc(p, ASCII_NBRZW);
61061d06d6bSBaptiste Daroussin 			} else {
61161d06d6bSBaptiste Daroussin 				uc = mchars_spec2cp(seq, sz);
61261d06d6bSBaptiste Daroussin 				if (uc > 0)
61361d06d6bSBaptiste Daroussin 					encode1(p, uc);
614*c1c95addSBrooks Davis 				else
615*c1c95addSBrooks Davis 					bufferc(p, ASCII_NBRZW);
61661d06d6bSBaptiste Daroussin 			}
61761d06d6bSBaptiste Daroussin 			continue;
6187295610fSBaptiste Daroussin 		case ESCAPE_UNDEF:
6197295610fSBaptiste Daroussin 			uc = *seq;
6207295610fSBaptiste Daroussin 			break;
62161d06d6bSBaptiste Daroussin 		case ESCAPE_FONTBOLD:
6226d38604fSBaptiste Daroussin 		case ESCAPE_FONTCB:
62361d06d6bSBaptiste Daroussin 			term_fontrepl(p, TERMFONT_BOLD);
62461d06d6bSBaptiste Daroussin 			continue;
62561d06d6bSBaptiste Daroussin 		case ESCAPE_FONTITALIC:
6266d38604fSBaptiste Daroussin 		case ESCAPE_FONTCI:
62761d06d6bSBaptiste Daroussin 			term_fontrepl(p, TERMFONT_UNDER);
62861d06d6bSBaptiste Daroussin 			continue;
62961d06d6bSBaptiste Daroussin 		case ESCAPE_FONTBI:
63061d06d6bSBaptiste Daroussin 			term_fontrepl(p, TERMFONT_BI);
63161d06d6bSBaptiste Daroussin 			continue;
63261d06d6bSBaptiste Daroussin 		case ESCAPE_FONT:
6336d38604fSBaptiste Daroussin 		case ESCAPE_FONTCR:
63461d06d6bSBaptiste Daroussin 		case ESCAPE_FONTROMAN:
63561d06d6bSBaptiste Daroussin 			term_fontrepl(p, TERMFONT_NONE);
63661d06d6bSBaptiste Daroussin 			continue;
63761d06d6bSBaptiste Daroussin 		case ESCAPE_FONTPREV:
63861d06d6bSBaptiste Daroussin 			term_fontlast(p);
63961d06d6bSBaptiste Daroussin 			continue;
64061d06d6bSBaptiste Daroussin 		case ESCAPE_BREAK:
64161d06d6bSBaptiste Daroussin 			bufferc(p, '\n');
64261d06d6bSBaptiste Daroussin 			continue;
64361d06d6bSBaptiste Daroussin 		case ESCAPE_NOSPACE:
64461d06d6bSBaptiste Daroussin 			if (p->flags & TERMP_BACKAFTER)
64561d06d6bSBaptiste Daroussin 				p->flags &= ~TERMP_BACKAFTER;
64661d06d6bSBaptiste Daroussin 			else if (*word == '\0')
64761d06d6bSBaptiste Daroussin 				p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE);
64861d06d6bSBaptiste Daroussin 			continue;
6497295610fSBaptiste Daroussin 		case ESCAPE_DEVICE:
6507295610fSBaptiste Daroussin 			if (p->type == TERMTYPE_PDF)
6517295610fSBaptiste Daroussin 				encode(p, "pdf", 3);
6527295610fSBaptiste Daroussin 			else if (p->type == TERMTYPE_PS)
6537295610fSBaptiste Daroussin 				encode(p, "ps", 2);
6547295610fSBaptiste Daroussin 			else if (p->enc == TERMENC_ASCII)
6557295610fSBaptiste Daroussin 				encode(p, "ascii", 5);
6567295610fSBaptiste Daroussin 			else
6577295610fSBaptiste Daroussin 				encode(p, "utf8", 4);
6587295610fSBaptiste Daroussin 			continue;
65961d06d6bSBaptiste Daroussin 		case ESCAPE_HORIZ:
660*c1c95addSBrooks Davis 			if (p->flags & TERMP_BACKAFTER) {
661*c1c95addSBrooks Davis 				p->flags &= ~TERMP_BACKAFTER;
662*c1c95addSBrooks Davis 				continue;
663*c1c95addSBrooks Davis 			}
66461d06d6bSBaptiste Daroussin 			if (*seq == '|') {
66561d06d6bSBaptiste Daroussin 				seq++;
66661d06d6bSBaptiste Daroussin 				uc = -p->col;
66761d06d6bSBaptiste Daroussin 			} else
66861d06d6bSBaptiste Daroussin 				uc = 0;
66961d06d6bSBaptiste Daroussin 			if (a2roffsu(seq, &su, SCALE_EM) == NULL)
67061d06d6bSBaptiste Daroussin 				continue;
67161d06d6bSBaptiste Daroussin 			uc += term_hen(p, &su);
672*c1c95addSBrooks Davis 			if (uc >= 0) {
673*c1c95addSBrooks Davis 				while (uc > 0) {
674*c1c95addSBrooks Davis 					uc -= term_len(p, 1);
675*c1c95addSBrooks Davis 					if (p->flags & TERMP_BACKBEFORE)
676*c1c95addSBrooks Davis 						p->flags &= ~TERMP_BACKBEFORE;
677*c1c95addSBrooks Davis 					else
67861d06d6bSBaptiste Daroussin 						bufferc(p, ASCII_NBRSP);
679*c1c95addSBrooks Davis 				}
680*c1c95addSBrooks Davis 				continue;
681*c1c95addSBrooks Davis 			}
682*c1c95addSBrooks Davis 			if (p->flags & TERMP_BACKBEFORE) {
683*c1c95addSBrooks Davis 				p->flags &= ~TERMP_BACKBEFORE;
684*c1c95addSBrooks Davis 				assert(p->col > 0);
685*c1c95addSBrooks Davis 				p->col--;
686*c1c95addSBrooks Davis 			}
687*c1c95addSBrooks Davis 			if (p->col >= (size_t)(-uc)) {
68861d06d6bSBaptiste Daroussin 				p->col += uc;
689*c1c95addSBrooks Davis 			} else {
69061d06d6bSBaptiste Daroussin 				uc += p->col;
69161d06d6bSBaptiste Daroussin 				p->col = 0;
69261d06d6bSBaptiste Daroussin 				if (p->tcol->offset > (size_t)(-uc)) {
69361d06d6bSBaptiste Daroussin 					p->ti += uc;
69461d06d6bSBaptiste Daroussin 					p->tcol->offset += uc;
69561d06d6bSBaptiste Daroussin 				} else {
69661d06d6bSBaptiste Daroussin 					p->ti -= p->tcol->offset;
69761d06d6bSBaptiste Daroussin 					p->tcol->offset = 0;
69861d06d6bSBaptiste Daroussin 				}
69961d06d6bSBaptiste Daroussin 			}
70061d06d6bSBaptiste Daroussin 			continue;
70161d06d6bSBaptiste Daroussin 		case ESCAPE_HLINE:
70261d06d6bSBaptiste Daroussin 			if ((cp = a2roffsu(seq, &su, SCALE_EM)) == NULL)
70361d06d6bSBaptiste Daroussin 				continue;
70461d06d6bSBaptiste Daroussin 			uc = term_hen(p, &su);
70561d06d6bSBaptiste Daroussin 			if (uc <= 0) {
70661d06d6bSBaptiste Daroussin 				if (p->tcol->rmargin <= p->tcol->offset)
70761d06d6bSBaptiste Daroussin 					continue;
70861d06d6bSBaptiste Daroussin 				lsz = p->tcol->rmargin - p->tcol->offset;
70961d06d6bSBaptiste Daroussin 			} else
71061d06d6bSBaptiste Daroussin 				lsz = uc;
71161d06d6bSBaptiste Daroussin 			if (*cp == seq[-1])
71261d06d6bSBaptiste Daroussin 				uc = -1;
71361d06d6bSBaptiste Daroussin 			else if (*cp == '\\') {
71461d06d6bSBaptiste Daroussin 				seq = cp + 1;
71561d06d6bSBaptiste Daroussin 				esc = mandoc_escape(&seq, &cp, &sz);
71661d06d6bSBaptiste Daroussin 				switch (esc) {
71761d06d6bSBaptiste Daroussin 				case ESCAPE_UNICODE:
71861d06d6bSBaptiste Daroussin 					uc = mchars_num2uc(cp + 1, sz - 1);
71961d06d6bSBaptiste Daroussin 					break;
72061d06d6bSBaptiste Daroussin 				case ESCAPE_NUMBERED:
72161d06d6bSBaptiste Daroussin 					uc = mchars_num2char(cp, sz);
72261d06d6bSBaptiste Daroussin 					break;
72361d06d6bSBaptiste Daroussin 				case ESCAPE_SPECIAL:
72461d06d6bSBaptiste Daroussin 					uc = mchars_spec2cp(cp, sz);
72561d06d6bSBaptiste Daroussin 					break;
7267295610fSBaptiste Daroussin 				case ESCAPE_UNDEF:
7277295610fSBaptiste Daroussin 					uc = *seq;
7287295610fSBaptiste Daroussin 					break;
72961d06d6bSBaptiste Daroussin 				default:
73061d06d6bSBaptiste Daroussin 					uc = -1;
73161d06d6bSBaptiste Daroussin 					break;
73261d06d6bSBaptiste Daroussin 				}
73361d06d6bSBaptiste Daroussin 			} else
73461d06d6bSBaptiste Daroussin 				uc = *cp;
73561d06d6bSBaptiste Daroussin 			if (uc < 0x20 || (uc > 0x7E && uc < 0xA0))
73661d06d6bSBaptiste Daroussin 				uc = '_';
73761d06d6bSBaptiste Daroussin 			if (p->enc == TERMENC_ASCII) {
73861d06d6bSBaptiste Daroussin 				cp = ascii_uc2str(uc);
73961d06d6bSBaptiste Daroussin 				csz = term_strlen(p, cp);
74061d06d6bSBaptiste Daroussin 				ssz = strlen(cp);
74161d06d6bSBaptiste Daroussin 			} else
74261d06d6bSBaptiste Daroussin 				csz = (*p->width)(p, uc);
74361d06d6bSBaptiste Daroussin 			while (lsz >= csz) {
74461d06d6bSBaptiste Daroussin 				if (p->enc == TERMENC_ASCII)
74561d06d6bSBaptiste Daroussin 					encode(p, cp, ssz);
74661d06d6bSBaptiste Daroussin 				else
74761d06d6bSBaptiste Daroussin 					encode1(p, uc);
74861d06d6bSBaptiste Daroussin 				lsz -= csz;
74961d06d6bSBaptiste Daroussin 			}
75061d06d6bSBaptiste Daroussin 			continue;
75161d06d6bSBaptiste Daroussin 		case ESCAPE_SKIPCHAR:
75261d06d6bSBaptiste Daroussin 			p->flags |= TERMP_BACKAFTER;
75361d06d6bSBaptiste Daroussin 			continue;
75461d06d6bSBaptiste Daroussin 		case ESCAPE_OVERSTRIKE:
75561d06d6bSBaptiste Daroussin 			cp = seq + sz;
75661d06d6bSBaptiste Daroussin 			while (seq < cp) {
75761d06d6bSBaptiste Daroussin 				if (*seq == '\\') {
75861d06d6bSBaptiste Daroussin 					mandoc_escape(&seq, NULL, NULL);
75961d06d6bSBaptiste Daroussin 					continue;
76061d06d6bSBaptiste Daroussin 				}
76161d06d6bSBaptiste Daroussin 				encode1(p, *seq++);
76261d06d6bSBaptiste Daroussin 				if (seq < cp) {
76361d06d6bSBaptiste Daroussin 					if (p->flags & TERMP_BACKBEFORE)
76461d06d6bSBaptiste Daroussin 						p->flags |= TERMP_BACKAFTER;
76561d06d6bSBaptiste Daroussin 					else
76661d06d6bSBaptiste Daroussin 						p->flags |= TERMP_BACKBEFORE;
76761d06d6bSBaptiste Daroussin 				}
76861d06d6bSBaptiste Daroussin 			}
76961d06d6bSBaptiste Daroussin 			/* Trim trailing backspace/blank pair. */
77061d06d6bSBaptiste Daroussin 			if (p->tcol->lastcol > 2 &&
77161d06d6bSBaptiste Daroussin 			    (p->tcol->buf[p->tcol->lastcol - 1] == ' ' ||
77261d06d6bSBaptiste Daroussin 			     p->tcol->buf[p->tcol->lastcol - 1] == '\t'))
77361d06d6bSBaptiste Daroussin 				p->tcol->lastcol -= 2;
77461d06d6bSBaptiste Daroussin 			if (p->col > p->tcol->lastcol)
77561d06d6bSBaptiste Daroussin 				p->col = p->tcol->lastcol;
77661d06d6bSBaptiste Daroussin 			continue;
777*c1c95addSBrooks Davis 		case ESCAPE_IGNORE:
778*c1c95addSBrooks Davis 			bufferc(p, ASCII_NBRZW);
779*c1c95addSBrooks Davis 			continue;
78061d06d6bSBaptiste Daroussin 		default:
78161d06d6bSBaptiste Daroussin 			continue;
78261d06d6bSBaptiste Daroussin 		}
78361d06d6bSBaptiste Daroussin 
78461d06d6bSBaptiste Daroussin 		/*
78561d06d6bSBaptiste Daroussin 		 * Common handling for Unicode and numbered
78661d06d6bSBaptiste Daroussin 		 * character escape sequences.
78761d06d6bSBaptiste Daroussin 		 */
78861d06d6bSBaptiste Daroussin 
78961d06d6bSBaptiste Daroussin 		if (p->enc == TERMENC_ASCII) {
79061d06d6bSBaptiste Daroussin 			cp = ascii_uc2str(uc);
79161d06d6bSBaptiste Daroussin 			encode(p, cp, strlen(cp));
79261d06d6bSBaptiste Daroussin 		} else {
79361d06d6bSBaptiste Daroussin 			if ((uc < 0x20 && uc != 0x09) ||
79461d06d6bSBaptiste Daroussin 			    (uc > 0x7E && uc < 0xA0))
79561d06d6bSBaptiste Daroussin 				uc = 0xFFFD;
79661d06d6bSBaptiste Daroussin 			encode1(p, uc);
79761d06d6bSBaptiste Daroussin 		}
79861d06d6bSBaptiste Daroussin 	}
79961d06d6bSBaptiste Daroussin 	p->flags &= ~TERMP_NBRWORD;
80061d06d6bSBaptiste Daroussin }
80161d06d6bSBaptiste Daroussin 
80261d06d6bSBaptiste Daroussin static void
80361d06d6bSBaptiste Daroussin adjbuf(struct termp_col *c, size_t sz)
80461d06d6bSBaptiste Daroussin {
80561d06d6bSBaptiste Daroussin 	if (c->maxcols == 0)
80661d06d6bSBaptiste Daroussin 		c->maxcols = 1024;
80761d06d6bSBaptiste Daroussin 	while (c->maxcols <= sz)
80861d06d6bSBaptiste Daroussin 		c->maxcols <<= 2;
80961d06d6bSBaptiste Daroussin 	c->buf = mandoc_reallocarray(c->buf, c->maxcols, sizeof(*c->buf));
81061d06d6bSBaptiste Daroussin }
81161d06d6bSBaptiste Daroussin 
81261d06d6bSBaptiste Daroussin static void
81361d06d6bSBaptiste Daroussin bufferc(struct termp *p, char c)
81461d06d6bSBaptiste Daroussin {
81561d06d6bSBaptiste Daroussin 	if (p->flags & TERMP_NOBUF) {
81661d06d6bSBaptiste Daroussin 		(*p->letter)(p, c);
81761d06d6bSBaptiste Daroussin 		return;
81861d06d6bSBaptiste Daroussin 	}
81961d06d6bSBaptiste Daroussin 	if (p->col + 1 >= p->tcol->maxcols)
82061d06d6bSBaptiste Daroussin 		adjbuf(p->tcol, p->col + 1);
82161d06d6bSBaptiste Daroussin 	if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
82261d06d6bSBaptiste Daroussin 		p->tcol->buf[p->col] = c;
82361d06d6bSBaptiste Daroussin 	if (p->tcol->lastcol < ++p->col)
82461d06d6bSBaptiste Daroussin 		p->tcol->lastcol = p->col;
82561d06d6bSBaptiste Daroussin }
82661d06d6bSBaptiste Daroussin 
827*c1c95addSBrooks Davis void
828*c1c95addSBrooks Davis term_tab_ref(struct termp *p)
829*c1c95addSBrooks Davis {
830*c1c95addSBrooks Davis 	if (p->tcol->lastcol && p->tcol->lastcol <= p->col &&
831*c1c95addSBrooks Davis 	    (p->flags & TERMP_NOBUF) == 0)
832*c1c95addSBrooks Davis 		bufferc(p, ASCII_TABREF);
833*c1c95addSBrooks Davis }
834*c1c95addSBrooks Davis 
83561d06d6bSBaptiste Daroussin /*
83661d06d6bSBaptiste Daroussin  * See encode().
83761d06d6bSBaptiste Daroussin  * Do this for a single (probably unicode) value.
83861d06d6bSBaptiste Daroussin  * Does not check for non-decorated glyphs.
83961d06d6bSBaptiste Daroussin  */
84061d06d6bSBaptiste Daroussin static void
84161d06d6bSBaptiste Daroussin encode1(struct termp *p, int c)
84261d06d6bSBaptiste Daroussin {
84361d06d6bSBaptiste Daroussin 	enum termfont	  f;
84461d06d6bSBaptiste Daroussin 
84561d06d6bSBaptiste Daroussin 	if (p->flags & TERMP_NOBUF) {
84661d06d6bSBaptiste Daroussin 		(*p->letter)(p, c);
84761d06d6bSBaptiste Daroussin 		return;
84861d06d6bSBaptiste Daroussin 	}
84961d06d6bSBaptiste Daroussin 
85061d06d6bSBaptiste Daroussin 	if (p->col + 7 >= p->tcol->maxcols)
85161d06d6bSBaptiste Daroussin 		adjbuf(p->tcol, p->col + 7);
85261d06d6bSBaptiste Daroussin 
85361d06d6bSBaptiste Daroussin 	f = (c == ASCII_HYPH || c > 127 || isgraph(c)) ?
85461d06d6bSBaptiste Daroussin 	    p->fontq[p->fonti] : TERMFONT_NONE;
85561d06d6bSBaptiste Daroussin 
85661d06d6bSBaptiste Daroussin 	if (p->flags & TERMP_BACKBEFORE) {
85761d06d6bSBaptiste Daroussin 		if (p->tcol->buf[p->col - 1] == ' ' ||
85861d06d6bSBaptiste Daroussin 		    p->tcol->buf[p->col - 1] == '\t')
85961d06d6bSBaptiste Daroussin 			p->col--;
86061d06d6bSBaptiste Daroussin 		else
86161d06d6bSBaptiste Daroussin 			p->tcol->buf[p->col++] = '\b';
86261d06d6bSBaptiste Daroussin 		p->flags &= ~TERMP_BACKBEFORE;
86361d06d6bSBaptiste Daroussin 	}
86461d06d6bSBaptiste Daroussin 	if (f == TERMFONT_UNDER || f == TERMFONT_BI) {
86561d06d6bSBaptiste Daroussin 		p->tcol->buf[p->col++] = '_';
86661d06d6bSBaptiste Daroussin 		p->tcol->buf[p->col++] = '\b';
86761d06d6bSBaptiste Daroussin 	}
86861d06d6bSBaptiste Daroussin 	if (f == TERMFONT_BOLD || f == TERMFONT_BI) {
86961d06d6bSBaptiste Daroussin 		if (c == ASCII_HYPH)
87061d06d6bSBaptiste Daroussin 			p->tcol->buf[p->col++] = '-';
87161d06d6bSBaptiste Daroussin 		else
87261d06d6bSBaptiste Daroussin 			p->tcol->buf[p->col++] = c;
87361d06d6bSBaptiste Daroussin 		p->tcol->buf[p->col++] = '\b';
87461d06d6bSBaptiste Daroussin 	}
87561d06d6bSBaptiste Daroussin 	if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
87661d06d6bSBaptiste Daroussin 		p->tcol->buf[p->col] = c;
87761d06d6bSBaptiste Daroussin 	if (p->tcol->lastcol < ++p->col)
87861d06d6bSBaptiste Daroussin 		p->tcol->lastcol = p->col;
87961d06d6bSBaptiste Daroussin 	if (p->flags & TERMP_BACKAFTER) {
88061d06d6bSBaptiste Daroussin 		p->flags |= TERMP_BACKBEFORE;
88161d06d6bSBaptiste Daroussin 		p->flags &= ~TERMP_BACKAFTER;
88261d06d6bSBaptiste Daroussin 	}
88361d06d6bSBaptiste Daroussin }
88461d06d6bSBaptiste Daroussin 
88561d06d6bSBaptiste Daroussin static void
88661d06d6bSBaptiste Daroussin encode(struct termp *p, const char *word, size_t sz)
88761d06d6bSBaptiste Daroussin {
88861d06d6bSBaptiste Daroussin 	size_t		  i;
88961d06d6bSBaptiste Daroussin 
89061d06d6bSBaptiste Daroussin 	if (p->flags & TERMP_NOBUF) {
89161d06d6bSBaptiste Daroussin 		for (i = 0; i < sz; i++)
89261d06d6bSBaptiste Daroussin 			(*p->letter)(p, word[i]);
89361d06d6bSBaptiste Daroussin 		return;
89461d06d6bSBaptiste Daroussin 	}
89561d06d6bSBaptiste Daroussin 
89661d06d6bSBaptiste Daroussin 	if (p->col + 2 + (sz * 5) >= p->tcol->maxcols)
89761d06d6bSBaptiste Daroussin 		adjbuf(p->tcol, p->col + 2 + (sz * 5));
89861d06d6bSBaptiste Daroussin 
89961d06d6bSBaptiste Daroussin 	for (i = 0; i < sz; i++) {
90061d06d6bSBaptiste Daroussin 		if (ASCII_HYPH == word[i] ||
90161d06d6bSBaptiste Daroussin 		    isgraph((unsigned char)word[i]))
90261d06d6bSBaptiste Daroussin 			encode1(p, word[i]);
90361d06d6bSBaptiste Daroussin 		else {
90461d06d6bSBaptiste Daroussin 			if (p->tcol->lastcol <= p->col ||
90561d06d6bSBaptiste Daroussin 			    (word[i] != ' ' && word[i] != ASCII_NBRSP))
90661d06d6bSBaptiste Daroussin 				p->tcol->buf[p->col] = word[i];
90761d06d6bSBaptiste Daroussin 			p->col++;
90861d06d6bSBaptiste Daroussin 
90961d06d6bSBaptiste Daroussin 			/*
91061d06d6bSBaptiste Daroussin 			 * Postpone the effect of \z while handling
91161d06d6bSBaptiste Daroussin 			 * an overstrike sequence from ascii_uc2str().
91261d06d6bSBaptiste Daroussin 			 */
91361d06d6bSBaptiste Daroussin 
91461d06d6bSBaptiste Daroussin 			if (word[i] == '\b' &&
91561d06d6bSBaptiste Daroussin 			    (p->flags & TERMP_BACKBEFORE)) {
91661d06d6bSBaptiste Daroussin 				p->flags &= ~TERMP_BACKBEFORE;
91761d06d6bSBaptiste Daroussin 				p->flags |= TERMP_BACKAFTER;
91861d06d6bSBaptiste Daroussin 			}
91961d06d6bSBaptiste Daroussin 		}
92061d06d6bSBaptiste Daroussin 	}
92161d06d6bSBaptiste Daroussin 	if (p->tcol->lastcol < p->col)
92261d06d6bSBaptiste Daroussin 		p->tcol->lastcol = p->col;
92361d06d6bSBaptiste Daroussin }
92461d06d6bSBaptiste Daroussin 
92561d06d6bSBaptiste Daroussin void
92661d06d6bSBaptiste Daroussin term_setwidth(struct termp *p, const char *wstr)
92761d06d6bSBaptiste Daroussin {
92861d06d6bSBaptiste Daroussin 	struct roffsu	 su;
92961d06d6bSBaptiste Daroussin 	int		 iop, width;
93061d06d6bSBaptiste Daroussin 
93161d06d6bSBaptiste Daroussin 	iop = 0;
93261d06d6bSBaptiste Daroussin 	width = 0;
93361d06d6bSBaptiste Daroussin 	if (NULL != wstr) {
93461d06d6bSBaptiste Daroussin 		switch (*wstr) {
93561d06d6bSBaptiste Daroussin 		case '+':
93661d06d6bSBaptiste Daroussin 			iop = 1;
93761d06d6bSBaptiste Daroussin 			wstr++;
93861d06d6bSBaptiste Daroussin 			break;
93961d06d6bSBaptiste Daroussin 		case '-':
94061d06d6bSBaptiste Daroussin 			iop = -1;
94161d06d6bSBaptiste Daroussin 			wstr++;
94261d06d6bSBaptiste Daroussin 			break;
94361d06d6bSBaptiste Daroussin 		default:
94461d06d6bSBaptiste Daroussin 			break;
94561d06d6bSBaptiste Daroussin 		}
94661d06d6bSBaptiste Daroussin 		if (a2roffsu(wstr, &su, SCALE_MAX) != NULL)
94761d06d6bSBaptiste Daroussin 			width = term_hspan(p, &su);
94861d06d6bSBaptiste Daroussin 		else
94961d06d6bSBaptiste Daroussin 			iop = 0;
95061d06d6bSBaptiste Daroussin 	}
95161d06d6bSBaptiste Daroussin 	(*p->setwidth)(p, iop, width);
95261d06d6bSBaptiste Daroussin }
95361d06d6bSBaptiste Daroussin 
95461d06d6bSBaptiste Daroussin size_t
95561d06d6bSBaptiste Daroussin term_len(const struct termp *p, size_t sz)
95661d06d6bSBaptiste Daroussin {
95761d06d6bSBaptiste Daroussin 
95861d06d6bSBaptiste Daroussin 	return (*p->width)(p, ' ') * sz;
95961d06d6bSBaptiste Daroussin }
96061d06d6bSBaptiste Daroussin 
96161d06d6bSBaptiste Daroussin static size_t
96261d06d6bSBaptiste Daroussin cond_width(const struct termp *p, int c, int *skip)
96361d06d6bSBaptiste Daroussin {
96461d06d6bSBaptiste Daroussin 
96561d06d6bSBaptiste Daroussin 	if (*skip) {
96661d06d6bSBaptiste Daroussin 		(*skip) = 0;
96761d06d6bSBaptiste Daroussin 		return 0;
96861d06d6bSBaptiste Daroussin 	} else
96961d06d6bSBaptiste Daroussin 		return (*p->width)(p, c);
97061d06d6bSBaptiste Daroussin }
97161d06d6bSBaptiste Daroussin 
97261d06d6bSBaptiste Daroussin size_t
97361d06d6bSBaptiste Daroussin term_strlen(const struct termp *p, const char *cp)
97461d06d6bSBaptiste Daroussin {
97561d06d6bSBaptiste Daroussin 	size_t		 sz, rsz, i;
97661d06d6bSBaptiste Daroussin 	int		 ssz, skip, uc;
97761d06d6bSBaptiste Daroussin 	const char	*seq, *rhs;
97861d06d6bSBaptiste Daroussin 	enum mandoc_esc	 esc;
979*c1c95addSBrooks Davis 	static const char rej[] = { '\\', ASCII_NBRSP, ASCII_NBRZW,
980*c1c95addSBrooks Davis 		ASCII_BREAK, ASCII_HYPH, ASCII_TABREF, '\0' };
98161d06d6bSBaptiste Daroussin 
98261d06d6bSBaptiste Daroussin 	/*
98361d06d6bSBaptiste Daroussin 	 * Account for escaped sequences within string length
98461d06d6bSBaptiste Daroussin 	 * calculations.  This follows the logic in term_word() as we
98561d06d6bSBaptiste Daroussin 	 * must calculate the width of produced strings.
98661d06d6bSBaptiste Daroussin 	 */
98761d06d6bSBaptiste Daroussin 
98861d06d6bSBaptiste Daroussin 	sz = 0;
98961d06d6bSBaptiste Daroussin 	skip = 0;
99061d06d6bSBaptiste Daroussin 	while ('\0' != *cp) {
99161d06d6bSBaptiste Daroussin 		rsz = strcspn(cp, rej);
99261d06d6bSBaptiste Daroussin 		for (i = 0; i < rsz; i++)
99361d06d6bSBaptiste Daroussin 			sz += cond_width(p, *cp++, &skip);
99461d06d6bSBaptiste Daroussin 
99561d06d6bSBaptiste Daroussin 		switch (*cp) {
99661d06d6bSBaptiste Daroussin 		case '\\':
99761d06d6bSBaptiste Daroussin 			cp++;
99861d06d6bSBaptiste Daroussin 			rhs = NULL;
9997295610fSBaptiste Daroussin 			esc = mandoc_escape(&cp, &seq, &ssz);
100061d06d6bSBaptiste Daroussin 			switch (esc) {
100161d06d6bSBaptiste Daroussin 			case ESCAPE_UNICODE:
100261d06d6bSBaptiste Daroussin 				uc = mchars_num2uc(seq + 1, ssz - 1);
100361d06d6bSBaptiste Daroussin 				break;
100461d06d6bSBaptiste Daroussin 			case ESCAPE_NUMBERED:
100561d06d6bSBaptiste Daroussin 				uc = mchars_num2char(seq, ssz);
100661d06d6bSBaptiste Daroussin 				if (uc < 0)
100761d06d6bSBaptiste Daroussin 					continue;
100861d06d6bSBaptiste Daroussin 				break;
100961d06d6bSBaptiste Daroussin 			case ESCAPE_SPECIAL:
101061d06d6bSBaptiste Daroussin 				if (p->enc == TERMENC_ASCII) {
101161d06d6bSBaptiste Daroussin 					rhs = mchars_spec2str(seq, ssz, &rsz);
101261d06d6bSBaptiste Daroussin 					if (rhs != NULL)
101361d06d6bSBaptiste Daroussin 						break;
101461d06d6bSBaptiste Daroussin 				} else {
101561d06d6bSBaptiste Daroussin 					uc = mchars_spec2cp(seq, ssz);
101661d06d6bSBaptiste Daroussin 					if (uc > 0)
101761d06d6bSBaptiste Daroussin 						sz += cond_width(p, uc, &skip);
101861d06d6bSBaptiste Daroussin 				}
101961d06d6bSBaptiste Daroussin 				continue;
10207295610fSBaptiste Daroussin 			case ESCAPE_UNDEF:
10217295610fSBaptiste Daroussin 				uc = *seq;
10227295610fSBaptiste Daroussin 				break;
10237295610fSBaptiste Daroussin 			case ESCAPE_DEVICE:
10247295610fSBaptiste Daroussin 				if (p->type == TERMTYPE_PDF) {
10257295610fSBaptiste Daroussin 					rhs = "pdf";
10267295610fSBaptiste Daroussin 					rsz = 3;
10277295610fSBaptiste Daroussin 				} else if (p->type == TERMTYPE_PS) {
10287295610fSBaptiste Daroussin 					rhs = "ps";
10297295610fSBaptiste Daroussin 					rsz = 2;
10307295610fSBaptiste Daroussin 				} else if (p->enc == TERMENC_ASCII) {
10317295610fSBaptiste Daroussin 					rhs = "ascii";
10327295610fSBaptiste Daroussin 					rsz = 5;
10337295610fSBaptiste Daroussin 				} else {
10347295610fSBaptiste Daroussin 					rhs = "utf8";
10357295610fSBaptiste Daroussin 					rsz = 4;
10367295610fSBaptiste Daroussin 				}
10377295610fSBaptiste Daroussin 				break;
103861d06d6bSBaptiste Daroussin 			case ESCAPE_SKIPCHAR:
103961d06d6bSBaptiste Daroussin 				skip = 1;
104061d06d6bSBaptiste Daroussin 				continue;
104161d06d6bSBaptiste Daroussin 			case ESCAPE_OVERSTRIKE:
104261d06d6bSBaptiste Daroussin 				rsz = 0;
104361d06d6bSBaptiste Daroussin 				rhs = seq + ssz;
104461d06d6bSBaptiste Daroussin 				while (seq < rhs) {
104561d06d6bSBaptiste Daroussin 					if (*seq == '\\') {
104661d06d6bSBaptiste Daroussin 						mandoc_escape(&seq, NULL, NULL);
104761d06d6bSBaptiste Daroussin 						continue;
104861d06d6bSBaptiste Daroussin 					}
104961d06d6bSBaptiste Daroussin 					i = (*p->width)(p, *seq++);
105061d06d6bSBaptiste Daroussin 					if (rsz < i)
105161d06d6bSBaptiste Daroussin 						rsz = i;
105261d06d6bSBaptiste Daroussin 				}
105361d06d6bSBaptiste Daroussin 				sz += rsz;
105461d06d6bSBaptiste Daroussin 				continue;
105561d06d6bSBaptiste Daroussin 			default:
105661d06d6bSBaptiste Daroussin 				continue;
105761d06d6bSBaptiste Daroussin 			}
105861d06d6bSBaptiste Daroussin 
105961d06d6bSBaptiste Daroussin 			/*
106061d06d6bSBaptiste Daroussin 			 * Common handling for Unicode and numbered
106161d06d6bSBaptiste Daroussin 			 * character escape sequences.
106261d06d6bSBaptiste Daroussin 			 */
106361d06d6bSBaptiste Daroussin 
106461d06d6bSBaptiste Daroussin 			if (rhs == NULL) {
106561d06d6bSBaptiste Daroussin 				if (p->enc == TERMENC_ASCII) {
106661d06d6bSBaptiste Daroussin 					rhs = ascii_uc2str(uc);
106761d06d6bSBaptiste Daroussin 					rsz = strlen(rhs);
106861d06d6bSBaptiste Daroussin 				} else {
106961d06d6bSBaptiste Daroussin 					if ((uc < 0x20 && uc != 0x09) ||
107061d06d6bSBaptiste Daroussin 					    (uc > 0x7E && uc < 0xA0))
107161d06d6bSBaptiste Daroussin 						uc = 0xFFFD;
107261d06d6bSBaptiste Daroussin 					sz += cond_width(p, uc, &skip);
107361d06d6bSBaptiste Daroussin 					continue;
107461d06d6bSBaptiste Daroussin 				}
107561d06d6bSBaptiste Daroussin 			}
107661d06d6bSBaptiste Daroussin 
107761d06d6bSBaptiste Daroussin 			if (skip) {
107861d06d6bSBaptiste Daroussin 				skip = 0;
107961d06d6bSBaptiste Daroussin 				break;
108061d06d6bSBaptiste Daroussin 			}
108161d06d6bSBaptiste Daroussin 
108261d06d6bSBaptiste Daroussin 			/*
108361d06d6bSBaptiste Daroussin 			 * Common handling for all escape sequences
108461d06d6bSBaptiste Daroussin 			 * printing more than one character.
108561d06d6bSBaptiste Daroussin 			 */
108661d06d6bSBaptiste Daroussin 
108761d06d6bSBaptiste Daroussin 			for (i = 0; i < rsz; i++)
108861d06d6bSBaptiste Daroussin 				sz += (*p->width)(p, *rhs++);
108961d06d6bSBaptiste Daroussin 			break;
109061d06d6bSBaptiste Daroussin 		case ASCII_NBRSP:
109161d06d6bSBaptiste Daroussin 			sz += cond_width(p, ' ', &skip);
109261d06d6bSBaptiste Daroussin 			cp++;
109361d06d6bSBaptiste Daroussin 			break;
109461d06d6bSBaptiste Daroussin 		case ASCII_HYPH:
109561d06d6bSBaptiste Daroussin 			sz += cond_width(p, '-', &skip);
109661d06d6bSBaptiste Daroussin 			cp++;
109761d06d6bSBaptiste Daroussin 			break;
109861d06d6bSBaptiste Daroussin 		default:
109961d06d6bSBaptiste Daroussin 			break;
110061d06d6bSBaptiste Daroussin 		}
110161d06d6bSBaptiste Daroussin 	}
110261d06d6bSBaptiste Daroussin 
110361d06d6bSBaptiste Daroussin 	return sz;
110461d06d6bSBaptiste Daroussin }
110561d06d6bSBaptiste Daroussin 
110661d06d6bSBaptiste Daroussin int
110761d06d6bSBaptiste Daroussin term_vspan(const struct termp *p, const struct roffsu *su)
110861d06d6bSBaptiste Daroussin {
110961d06d6bSBaptiste Daroussin 	double		 r;
111061d06d6bSBaptiste Daroussin 	int		 ri;
111161d06d6bSBaptiste Daroussin 
111261d06d6bSBaptiste Daroussin 	switch (su->unit) {
111361d06d6bSBaptiste Daroussin 	case SCALE_BU:
111461d06d6bSBaptiste Daroussin 		r = su->scale / 40.0;
111561d06d6bSBaptiste Daroussin 		break;
111661d06d6bSBaptiste Daroussin 	case SCALE_CM:
111761d06d6bSBaptiste Daroussin 		r = su->scale * 6.0 / 2.54;
111861d06d6bSBaptiste Daroussin 		break;
111961d06d6bSBaptiste Daroussin 	case SCALE_FS:
112061d06d6bSBaptiste Daroussin 		r = su->scale * 65536.0 / 40.0;
112161d06d6bSBaptiste Daroussin 		break;
112261d06d6bSBaptiste Daroussin 	case SCALE_IN:
112361d06d6bSBaptiste Daroussin 		r = su->scale * 6.0;
112461d06d6bSBaptiste Daroussin 		break;
112561d06d6bSBaptiste Daroussin 	case SCALE_MM:
112661d06d6bSBaptiste Daroussin 		r = su->scale * 0.006;
112761d06d6bSBaptiste Daroussin 		break;
112861d06d6bSBaptiste Daroussin 	case SCALE_PC:
112961d06d6bSBaptiste Daroussin 		r = su->scale;
113061d06d6bSBaptiste Daroussin 		break;
113161d06d6bSBaptiste Daroussin 	case SCALE_PT:
113261d06d6bSBaptiste Daroussin 		r = su->scale / 12.0;
113361d06d6bSBaptiste Daroussin 		break;
113461d06d6bSBaptiste Daroussin 	case SCALE_EN:
113561d06d6bSBaptiste Daroussin 	case SCALE_EM:
113661d06d6bSBaptiste Daroussin 		r = su->scale * 0.6;
113761d06d6bSBaptiste Daroussin 		break;
113861d06d6bSBaptiste Daroussin 	case SCALE_VS:
113961d06d6bSBaptiste Daroussin 		r = su->scale;
114061d06d6bSBaptiste Daroussin 		break;
114161d06d6bSBaptiste Daroussin 	default:
114261d06d6bSBaptiste Daroussin 		abort();
114361d06d6bSBaptiste Daroussin 	}
114461d06d6bSBaptiste Daroussin 	ri = r > 0.0 ? r + 0.4995 : r - 0.4995;
114561d06d6bSBaptiste Daroussin 	return ri < 66 ? ri : 1;
114661d06d6bSBaptiste Daroussin }
114761d06d6bSBaptiste Daroussin 
114861d06d6bSBaptiste Daroussin /*
114961d06d6bSBaptiste Daroussin  * Convert a scaling width to basic units, rounding towards 0.
115061d06d6bSBaptiste Daroussin  */
115161d06d6bSBaptiste Daroussin int
115261d06d6bSBaptiste Daroussin term_hspan(const struct termp *p, const struct roffsu *su)
115361d06d6bSBaptiste Daroussin {
115461d06d6bSBaptiste Daroussin 
115561d06d6bSBaptiste Daroussin 	return (*p->hspan)(p, su);
115661d06d6bSBaptiste Daroussin }
115761d06d6bSBaptiste Daroussin 
115861d06d6bSBaptiste Daroussin /*
115961d06d6bSBaptiste Daroussin  * Convert a scaling width to basic units, rounding to closest.
116061d06d6bSBaptiste Daroussin  */
116161d06d6bSBaptiste Daroussin int
116261d06d6bSBaptiste Daroussin term_hen(const struct termp *p, const struct roffsu *su)
116361d06d6bSBaptiste Daroussin {
116461d06d6bSBaptiste Daroussin 	int bu;
116561d06d6bSBaptiste Daroussin 
116661d06d6bSBaptiste Daroussin 	if ((bu = (*p->hspan)(p, su)) >= 0)
116761d06d6bSBaptiste Daroussin 		return (bu + 11) / 24;
116861d06d6bSBaptiste Daroussin 	else
116961d06d6bSBaptiste Daroussin 		return -((-bu + 11) / 24);
117061d06d6bSBaptiste Daroussin }
1171