xref: /openbsd-src/usr.bin/mandoc/term.c (revision d9a51c353c88dac7b4a389c112b4cfe97b8e3a46)
1*d9a51c35Sjmc /* $OpenBSD: term.c,v 1.151 2022/12/26 19:16:02 jmc Exp $ */
2f73abda9Skristaps /*
36c5a416aSschwarze  * Copyright (c) 2010-2022 Ingo Schwarze <schwarze@openbsd.org>
4a5e11edeSschwarze  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
5f73abda9Skristaps  *
6f73abda9Skristaps  * Permission to use, copy, modify, and distribute this software for any
7a6464425Sschwarze  * purpose with or without fee is hereby granted, provided that the above
8a6464425Sschwarze  * copyright notice and this permission notice appear in all copies.
9f73abda9Skristaps  *
102a238f45Sschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11a6464425Sschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
122a238f45Sschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13a6464425Sschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14a6464425Sschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15a6464425Sschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16a6464425Sschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17f73abda9Skristaps  */
18fa70b73eSschwarze #include <sys/types.h>
19fa70b73eSschwarze 
20f73abda9Skristaps #include <assert.h>
21fa70b73eSschwarze #include <ctype.h>
223139a193Sschwarze #include <stdint.h>
23f73abda9Skristaps #include <stdio.h>
24f73abda9Skristaps #include <stdlib.h>
25f73abda9Skristaps #include <string.h>
26f73abda9Skristaps 
276e03d529Sschwarze #include "mandoc.h"
284f4f7972Sschwarze #include "mandoc_aux.h"
294175bdabSschwarze #include "out.h"
30f73abda9Skristaps #include "term.h"
314175bdabSschwarze #include "main.h"
32f73abda9Skristaps 
337eac745dSschwarze static	size_t		 cond_width(const struct termp *, int, int *);
34e93ea447Sschwarze static	void		 adjbuf(struct termp_col *, size_t);
35fa70b73eSschwarze static	void		 bufferc(struct termp *, char);
36fa70b73eSschwarze static	void		 encode(struct termp *, const char *, size_t);
37a5e11edeSschwarze static	void		 encode1(struct termp *, int);
3824f1eaadSschwarze static	void		 endline(struct termp *);
399a1da370Sschwarze static	void		 term_field(struct termp *, size_t, size_t);
403139a193Sschwarze static	void		 term_fill(struct termp *, size_t *, size_t *,
413139a193Sschwarze 				size_t);
42f73abda9Skristaps 
4349aff9f8Sschwarze 
446ae2e8acSschwarze void
term_setcol(struct termp * p,size_t maxtcol)458afa4451Sschwarze term_setcol(struct termp *p, size_t maxtcol)
468afa4451Sschwarze {
478afa4451Sschwarze 	if (maxtcol > p->maxtcol) {
488afa4451Sschwarze 		p->tcols = mandoc_recallocarray(p->tcols,
498afa4451Sschwarze 		    p->maxtcol, maxtcol, sizeof(*p->tcols));
508afa4451Sschwarze 		p->maxtcol = maxtcol;
518afa4451Sschwarze 	}
528afa4451Sschwarze 	p->lasttcol = maxtcol - 1;
538afa4451Sschwarze 	p->tcol = p->tcols;
548afa4451Sschwarze }
558afa4451Sschwarze 
568afa4451Sschwarze void
term_free(struct termp * p)57f73abda9Skristaps term_free(struct termp *p)
58f73abda9Skristaps {
59888caeecSschwarze 	term_tab_free();
60e93ea447Sschwarze 	for (p->tcol = p->tcols; p->tcol < p->tcols + p->maxtcol; p->tcol++)
61e93ea447Sschwarze 		free(p->tcol->buf);
62e93ea447Sschwarze 	free(p->tcols);
63c48a0735Sschwarze 	free(p->fontq);
64f73abda9Skristaps 	free(p);
65f73abda9Skristaps }
66f73abda9Skristaps 
67f95d589eSschwarze void
term_begin(struct termp * p,term_margin head,term_margin foot,const struct roff_meta * arg)68f95d589eSschwarze term_begin(struct termp *p, term_margin head,
692a238f45Sschwarze 		term_margin foot, const struct roff_meta *arg)
70f95d589eSschwarze {
71f95d589eSschwarze 
72f95d589eSschwarze 	p->headf = head;
73f95d589eSschwarze 	p->footf = foot;
74f95d589eSschwarze 	p->argf = arg;
75f95d589eSschwarze 	(*p->begin)(p);
76f95d589eSschwarze }
77f95d589eSschwarze 
78f95d589eSschwarze void
term_end(struct termp * p)79f95d589eSschwarze term_end(struct termp *p)
80f95d589eSschwarze {
81f95d589eSschwarze 
82f95d589eSschwarze 	(*p->end)(p);
83f95d589eSschwarze }
84f95d589eSschwarze 
85f73abda9Skristaps /*
86eeb5fd14Sschwarze  * Flush a chunk of text.  By default, break the output line each time
87eeb5fd14Sschwarze  * the right margin is reached, and continue output on the next line
88eeb5fd14Sschwarze  * at the same offset as the chunk itself.  By default, also break the
893139a193Sschwarze  * output line at the end of the chunk.  There are many flags modifying
903139a193Sschwarze  * this behaviour, see the comments in the body of the function.
91f73abda9Skristaps  */
92f73abda9Skristaps void
term_flushln(struct termp * p)93f73abda9Skristaps term_flushln(struct termp *p)
94f73abda9Skristaps {
953139a193Sschwarze 	size_t	 vbl;      /* Number of blanks to prepend to the output. */
963139a193Sschwarze 	size_t	 vbr;      /* Actual visual position of the end of field. */
973139a193Sschwarze 	size_t	 vfield;   /* Desired visual field width. */
983139a193Sschwarze 	size_t	 vtarget;  /* Desired visual position of the right margin. */
993139a193Sschwarze 	size_t	 ic;       /* Character position in the input buffer. */
1003139a193Sschwarze 	size_t	 nbr;      /* Number of characters to print in this field. */
1013139a193Sschwarze 
1023139a193Sschwarze 	/*
1033139a193Sschwarze 	 * Normally, start writing at the left margin, but with the
1043139a193Sschwarze 	 * NOPAD flag, start writing at the current position instead.
1053139a193Sschwarze 	 */
106f73abda9Skristaps 
107e93ea447Sschwarze 	vbl = (p->flags & TERMP_NOPAD) || p->tcol->offset < p->viscol ?
108e93ea447Sschwarze 	    0 : p->tcol->offset - p->viscol;
109771c54bcSschwarze 	if (p->minbl && vbl < p->minbl)
110771c54bcSschwarze 		vbl = p->minbl;
1118f1c9461Sschwarze 
112f79fc00cSflorian 	if ((p->flags & TERMP_MULTICOL) == 0)
1138f9c9bf2Sschwarze 		p->tcol->col = 0;
1143139a193Sschwarze 
1153139a193Sschwarze 	/* Loop over output lines. */
1163139a193Sschwarze 
1173139a193Sschwarze 	for (;;) {
1183139a193Sschwarze 		vfield = p->tcol->rmargin > p->viscol + vbl ?
1193139a193Sschwarze 		    p->tcol->rmargin - p->viscol - vbl : 0;
1208f9c9bf2Sschwarze 
1218f1c9461Sschwarze 		/*
1223139a193Sschwarze 		 * Normally, break the line at the the right margin
1233139a193Sschwarze 		 * of the field, but with the NOBREAK flag, only
1243139a193Sschwarze 		 * break it at the max right margin of the screen,
1253139a193Sschwarze 		 * and with the BRNEVER flag, never break it at all.
126aa4c622fSschwarze 		 */
1278f9c9bf2Sschwarze 
1289a1da370Sschwarze 		vtarget = (p->flags & TERMP_NOBREAK) == 0 ? vfield :
1293139a193Sschwarze 		    p->maxrmargin > p->viscol + vbl ?
1303139a193Sschwarze 		    p->maxrmargin - p->viscol - vbl : 0;
13179fab53bSschwarze 
13279fab53bSschwarze 		/*
1333139a193Sschwarze 		 * Figure out how much text will fit in the field.
1343139a193Sschwarze 		 * If there is whitespace only, print nothing.
13579fab53bSschwarze 		 */
13679fab53bSschwarze 
1379a1da370Sschwarze 		term_fill(p, &nbr, &vbr,
1389a1da370Sschwarze 		    p->flags & TERMP_BRNEVER ? SIZE_MAX : vtarget);
1393139a193Sschwarze 		if (nbr == 0)
14079fab53bSschwarze 			break;
14106f7b709Sschwarze 
142b306689cSschwarze 		/*
143b306689cSschwarze 		 * With the CENTER or RIGHT flag, increase the indentation
144b306689cSschwarze 		 * to center the text between the left and right margins
145b306689cSschwarze 		 * or to adjust it to the right margin, respectively.
146b306689cSschwarze 		 */
147b306689cSschwarze 
148b306689cSschwarze 		if (vbr < vtarget) {
149b306689cSschwarze 			if (p->flags & TERMP_CENTER)
150b306689cSschwarze 				vbl += (vtarget - vbr) / 2;
151b306689cSschwarze 			else if (p->flags & TERMP_RIGHT)
152b306689cSschwarze 				vbl += vtarget - vbr;
153b306689cSschwarze 		}
154b306689cSschwarze 
155b306689cSschwarze 		/* Finally, print the field content. */
156b306689cSschwarze 
1579a1da370Sschwarze 		term_field(p, vbl, nbr);
15818bbf166Sschwarze 		if (vbr < vtarget)
15918bbf166Sschwarze 			p->tcol->taboff += vbr;
16018bbf166Sschwarze 		else
16118bbf166Sschwarze 			p->tcol->taboff += vtarget;
16218bbf166Sschwarze 		p->tcol->taboff += (*p->width)(p, ' ');
1633139a193Sschwarze 
1643139a193Sschwarze 		/*
1653139a193Sschwarze 		 * If there is no text left in the field, exit the loop.
1663139a193Sschwarze 		 * If the BRTRSP flag is set, consider trailing
1673139a193Sschwarze 		 * whitespace significant when deciding whether
1683139a193Sschwarze 		 * the field fits or not.
1693139a193Sschwarze 		 */
1703139a193Sschwarze 
1713139a193Sschwarze 		for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
1723139a193Sschwarze 			switch (p->tcol->buf[ic]) {
1733139a193Sschwarze 			case '\t':
1743139a193Sschwarze 				if (p->flags & TERMP_BRTRSP)
1753139a193Sschwarze 					vbr = term_tab_next(vbr);
17606f7b709Sschwarze 				continue;
1773139a193Sschwarze 			case ' ':
1783139a193Sschwarze 				if (p->flags & TERMP_BRTRSP)
1793139a193Sschwarze 					vbr += (*p->width)(p, ' ');
1806167ec38Sschwarze 				continue;
1813139a193Sschwarze 			case '\n':
18218bbf166Sschwarze 			case ASCII_NBRZW:
1833139a193Sschwarze 			case ASCII_BREAK:
18418bbf166Sschwarze 			case ASCII_TABREF:
1853139a193Sschwarze 				continue;
1863139a193Sschwarze 			default:
187aa4c622fSschwarze 				break;
1883139a193Sschwarze 			}
1893139a193Sschwarze 			break;
1903139a193Sschwarze 		}
1913139a193Sschwarze 		if (ic == p->tcol->lastcol)
1923139a193Sschwarze 			break;
1933139a193Sschwarze 
1943139a193Sschwarze 		/*
195*d9a51c35Sjmc 		 * At the location of an automatic line break, input
1963139a193Sschwarze 		 * space characters are consumed by the line break.
1973139a193Sschwarze 		 */
1983139a193Sschwarze 
1998afa4451Sschwarze 		while (p->tcol->col < p->tcol->lastcol &&
2008f9c9bf2Sschwarze 		    p->tcol->buf[p->tcol->col] == ' ')
2018f9c9bf2Sschwarze 			p->tcol->col++;
202a9b41670Sschwarze 
203a9b41670Sschwarze 		/*
2043139a193Sschwarze 		 * In multi-column mode, leave the rest of the text
2053139a193Sschwarze 		 * in the buffer to be handled by a subsequent
2063139a193Sschwarze 		 * invocation, such that the other columns of the
2073139a193Sschwarze 		 * table can be handled first.
2083139a193Sschwarze 		 * In single-column mode, simply break the line.
209a9b41670Sschwarze 		 */
2106167ec38Sschwarze 
2116167ec38Sschwarze 		if (p->flags & TERMP_MULTICOL)
2126167ec38Sschwarze 			return;
2136167ec38Sschwarze 
2146167ec38Sschwarze 		endline(p);
215b822ca0dSschwarze 
2162c61c325Sschwarze 		/*
2173139a193Sschwarze 		 * Normally, start the next line at the same indentation
2183139a193Sschwarze 		 * as this one, but with the BRIND flag, start it at the
2193139a193Sschwarze 		 * right margin instead.  This is used together with
2203139a193Sschwarze 		 * NOBREAK for the tags in various kinds of tagged lists.
2212c61c325Sschwarze 		 */
2228f9c9bf2Sschwarze 
2233139a193Sschwarze 		vbl = p->flags & TERMP_BRIND ?
2243139a193Sschwarze 		    p->tcol->rmargin : p->tcol->offset;
2253139a193Sschwarze 	}
2263139a193Sschwarze 
2273139a193Sschwarze 	/* Reset output state in preparation for the next field. */
2282c61c325Sschwarze 
2298afa4451Sschwarze 	p->col = p->tcol->col = p->tcol->lastcol = 0;
230771c54bcSschwarze 	p->minbl = p->trailspace;
23124f1eaadSschwarze 	p->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE | TERMP_NOPAD);
232bb89415cSschwarze 
2338afa4451Sschwarze 	if (p->flags & TERMP_MULTICOL)
2348afa4451Sschwarze 		return;
2358afa4451Sschwarze 
2363139a193Sschwarze 	/*
2373139a193Sschwarze 	 * The HANG flag means that the next field
2383139a193Sschwarze 	 * always follows on the same line.
2393139a193Sschwarze 	 * The NOBREAK flag means that the next field
2403139a193Sschwarze 	 * follows on the same line unless the field was overrun.
2413139a193Sschwarze 	 * Normally, break the line at the end of each field.
2423139a193Sschwarze 	 */
2438f9c9bf2Sschwarze 
2443139a193Sschwarze 	if ((p->flags & TERMP_HANG) == 0 &&
2453139a193Sschwarze 	    ((p->flags & TERMP_NOBREAK) == 0 ||
2463139a193Sschwarze 	     vbr + term_len(p, p->trailspace) > vfield))
24724f1eaadSschwarze 		endline(p);
24824f1eaadSschwarze }
24924f1eaadSschwarze 
2503139a193Sschwarze /*
2513139a193Sschwarze  * Store the number of input characters to print in this field in *nbr
2523139a193Sschwarze  * and their total visual width to print in *vbr.
2533139a193Sschwarze  * If there is only whitespace in the field, both remain zero.
2543139a193Sschwarze  * The desired visual width of the field is provided by vtarget.
2553139a193Sschwarze  * If the first word is longer, the field will be overrun.
2563139a193Sschwarze  */
2573139a193Sschwarze static void
term_fill(struct termp * p,size_t * nbr,size_t * vbr,size_t vtarget)2583139a193Sschwarze term_fill(struct termp *p, size_t *nbr, size_t *vbr, size_t vtarget)
2593139a193Sschwarze {
2603139a193Sschwarze 	size_t	 ic;        /* Character position in the input buffer. */
2613139a193Sschwarze 	size_t	 vis;       /* Visual position of the current character. */
2623139a193Sschwarze 	size_t	 vn;        /* Visual position of the next character. */
2633139a193Sschwarze 	int	 breakline; /* Break at the end of this word. */
2643139a193Sschwarze 	int	 graph;     /* Last character was non-blank. */
26518bbf166Sschwarze 	int	 taboff;    /* Temporary offset for literal tabs. */
2663139a193Sschwarze 
2673139a193Sschwarze 	*nbr = *vbr = vis = 0;
2683139a193Sschwarze 	breakline = graph = 0;
26918bbf166Sschwarze 	taboff = p->tcol->taboff;
2703139a193Sschwarze 	for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
2713139a193Sschwarze 		switch (p->tcol->buf[ic]) {
2723139a193Sschwarze 		case '\b':  /* Escape \o (overstrike) or backspace markup. */
2733139a193Sschwarze 			assert(ic > 0);
2743139a193Sschwarze 			vis -= (*p->width)(p, p->tcol->buf[ic - 1]);
2753139a193Sschwarze 			continue;
2763139a193Sschwarze 
2773139a193Sschwarze 		case ' ':
2783139a193Sschwarze 		case ASCII_BREAK:  /* Escape \: (breakpoint). */
2793139a193Sschwarze 			vn = vis;
280dcd30e5bSschwarze 			if (p->tcol->buf[ic] == ' ')
281dcd30e5bSschwarze 				vn += (*p->width)(p, ' ');
2823139a193Sschwarze 			/* Can break at the end of a word. */
2833139a193Sschwarze 			if (breakline || vn > vtarget)
2843139a193Sschwarze 				break;
2853139a193Sschwarze 			if (graph) {
2863139a193Sschwarze 				*nbr = ic;
2873139a193Sschwarze 				*vbr = vis;
2883139a193Sschwarze 				graph = 0;
2893139a193Sschwarze 			}
2903139a193Sschwarze 			vis = vn;
2913139a193Sschwarze 			continue;
2923139a193Sschwarze 
2933139a193Sschwarze 		case '\n':  /* Escape \p (break at the end of the word). */
2943139a193Sschwarze 			breakline = 1;
2953139a193Sschwarze 			continue;
2963139a193Sschwarze 
2973139a193Sschwarze 		case ASCII_HYPH:  /* Breakable hyphen. */
2983139a193Sschwarze 			graph = 1;
2993139a193Sschwarze 			/*
3003139a193Sschwarze 			 * We are about to decide whether to break the
3013139a193Sschwarze 			 * line or not, so we no longer need this hyphen
3023139a193Sschwarze 			 * to be marked as breakable.  Put back a real
3033139a193Sschwarze 			 * hyphen such that we get the correct width.
3043139a193Sschwarze 			 */
3053139a193Sschwarze 			p->tcol->buf[ic] = '-';
3063139a193Sschwarze 			vis += (*p->width)(p, '-');
3073139a193Sschwarze 			if (vis > vtarget) {
3083139a193Sschwarze 				ic++;
3093139a193Sschwarze 				break;
3103139a193Sschwarze 			}
3113139a193Sschwarze 			*nbr = ic + 1;
3123139a193Sschwarze 			*vbr = vis;
3133139a193Sschwarze 			continue;
3143139a193Sschwarze 
31518bbf166Sschwarze 		case ASCII_TABREF:
31618bbf166Sschwarze 			taboff = -vis - (*p->width)(p, ' ');
31718bbf166Sschwarze 			continue;
31818bbf166Sschwarze 
319dcd30e5bSschwarze 		default:
320dcd30e5bSschwarze 			switch (p->tcol->buf[ic]) {
321dcd30e5bSschwarze 			case '\t':
32218bbf166Sschwarze 				if (taboff < 0 && (size_t)-taboff > vis)
32318bbf166Sschwarze 					vis = 0;
32418bbf166Sschwarze 				else
32518bbf166Sschwarze 					vis += taboff;
326dcd30e5bSschwarze 				vis = term_tab_next(vis);
32718bbf166Sschwarze 				vis -= taboff;
328dcd30e5bSschwarze 				break;
329e0e72893Sschwarze 			case ASCII_NBRZW:  /* Non-breakable zero-width. */
330e0e72893Sschwarze 				break;
3313139a193Sschwarze 			case ASCII_NBRSP:  /* Non-breakable space. */
3323139a193Sschwarze 				p->tcol->buf[ic] = ' ';
3333139a193Sschwarze 				/* FALLTHROUGH */
3343139a193Sschwarze 			default:  /* Printable character. */
3353139a193Sschwarze 				vis += (*p->width)(p, p->tcol->buf[ic]);
336dcd30e5bSschwarze 				break;
337dcd30e5bSschwarze 			}
338dcd30e5bSschwarze 			graph = 1;
3393139a193Sschwarze 			if (vis > vtarget && *nbr > 0)
3403139a193Sschwarze 				return;
3413139a193Sschwarze 			continue;
3423139a193Sschwarze 		}
3433139a193Sschwarze 		break;
3443139a193Sschwarze 	}
3453139a193Sschwarze 
3463139a193Sschwarze 	/*
3473139a193Sschwarze 	 * If the last word extends to the end of the field without any
3483139a193Sschwarze 	 * trailing whitespace, the loop could not check yet whether it
3493139a193Sschwarze 	 * can remain on this line.  So do the check now.
3503139a193Sschwarze 	 */
3513139a193Sschwarze 
3523139a193Sschwarze 	if (graph && (vis <= vtarget || *nbr == 0)) {
3533139a193Sschwarze 		*nbr = ic;
3543139a193Sschwarze 		*vbr = vis;
3553139a193Sschwarze 	}
3563139a193Sschwarze }
3573139a193Sschwarze 
3583139a193Sschwarze /*
3593139a193Sschwarze  * Print the contents of one field
3603139a193Sschwarze  * with an indentation of	 vbl	  visual columns,
3619a1da370Sschwarze  * and an input string length of nbr	  characters.
3623139a193Sschwarze  */
3633139a193Sschwarze static void
term_field(struct termp * p,size_t vbl,size_t nbr)3649a1da370Sschwarze term_field(struct termp *p, size_t vbl, size_t nbr)
3653139a193Sschwarze {
3663139a193Sschwarze 	size_t	 ic;	/* Character position in the input buffer. */
3673139a193Sschwarze 	size_t	 vis;	/* Visual position of the current character. */
368dd2df837Sschwarze 	size_t	 vt;	/* Visual position including tab offset. */
3693139a193Sschwarze 	size_t	 dv;	/* Visual width of the current character. */
37018bbf166Sschwarze 	int	 taboff; /* Temporary offset for literal tabs. */
3713139a193Sschwarze 
3723139a193Sschwarze 	vis = 0;
37318bbf166Sschwarze 	taboff = p->tcol->taboff;
3743139a193Sschwarze 	for (ic = p->tcol->col; ic < nbr; ic++) {
3753139a193Sschwarze 
3763139a193Sschwarze 		/*
3773139a193Sschwarze 		 * To avoid the printing of trailing whitespace,
3783139a193Sschwarze 		 * do not print whitespace right away, only count it.
3793139a193Sschwarze 		 */
3803139a193Sschwarze 
3813139a193Sschwarze 		switch (p->tcol->buf[ic]) {
3823139a193Sschwarze 		case '\n':
3833139a193Sschwarze 		case ASCII_BREAK:
384e0e72893Sschwarze 		case ASCII_NBRZW:
3853139a193Sschwarze 			continue;
38618bbf166Sschwarze 		case ASCII_TABREF:
38718bbf166Sschwarze 			taboff = -vis - (*p->width)(p, ' ');
38818bbf166Sschwarze 			continue;
3893139a193Sschwarze 		case '\t':
3903139a193Sschwarze 		case ' ':
3913139a193Sschwarze 		case ASCII_NBRSP:
392dd2df837Sschwarze 			if (p->tcol->buf[ic] == '\t') {
39318bbf166Sschwarze 				if (taboff < 0 && (size_t)-taboff > vis)
39418bbf166Sschwarze 					vt = 0;
39518bbf166Sschwarze 				else
39618bbf166Sschwarze 					vt = vis + taboff;
397dd2df837Sschwarze 				dv = term_tab_next(vt) - vt;
398dd2df837Sschwarze 			} else
39978d1f2aeSschwarze 				dv = (*p->width)(p, ' ');
40078d1f2aeSschwarze 			vbl += dv;
40178d1f2aeSschwarze 			vis += dv;
4023139a193Sschwarze 			continue;
4033139a193Sschwarze 		default:
4043139a193Sschwarze 			break;
4053139a193Sschwarze 		}
4063139a193Sschwarze 
4073139a193Sschwarze 		/*
4083139a193Sschwarze 		 * We found a non-blank character to print,
4093139a193Sschwarze 		 * so write preceding white space now.
4103139a193Sschwarze 		 */
4113139a193Sschwarze 
4123139a193Sschwarze 		if (vbl > 0) {
4133139a193Sschwarze 			(*p->advance)(p, vbl);
4143139a193Sschwarze 			p->viscol += vbl;
4153139a193Sschwarze 			vbl = 0;
4163139a193Sschwarze 		}
4173139a193Sschwarze 
4183139a193Sschwarze 		/* Print the character and adjust the visual position. */
4193139a193Sschwarze 
4203139a193Sschwarze 		(*p->letter)(p, p->tcol->buf[ic]);
4213139a193Sschwarze 		if (p->tcol->buf[ic] == '\b') {
4223139a193Sschwarze 			dv = (*p->width)(p, p->tcol->buf[ic - 1]);
4233139a193Sschwarze 			p->viscol -= dv;
4243139a193Sschwarze 			vis -= dv;
4253139a193Sschwarze 		} else {
4263139a193Sschwarze 			dv = (*p->width)(p, p->tcol->buf[ic]);
4273139a193Sschwarze 			p->viscol += dv;
4283139a193Sschwarze 			vis += dv;
4293139a193Sschwarze 		}
4303139a193Sschwarze 	}
4313139a193Sschwarze 	p->tcol->col = nbr;
4323139a193Sschwarze }
4333139a193Sschwarze 
43424f1eaadSschwarze static void
endline(struct termp * p)43524f1eaadSschwarze endline(struct termp *p)
43624f1eaadSschwarze {
43724f1eaadSschwarze 	if ((p->flags & (TERMP_NEWMC | TERMP_ENDMC)) == TERMP_ENDMC) {
43824f1eaadSschwarze 		p->mc = NULL;
43924f1eaadSschwarze 		p->flags &= ~TERMP_ENDMC;
44024f1eaadSschwarze 	}
44124f1eaadSschwarze 	if (p->mc != NULL) {
44224f1eaadSschwarze 		if (p->viscol && p->maxrmargin >= p->viscol)
44324f1eaadSschwarze 			(*p->advance)(p, p->maxrmargin - p->viscol + 1);
44424f1eaadSschwarze 		p->flags |= TERMP_NOBUF | TERMP_NOSPACE;
44524f1eaadSschwarze 		term_word(p, p->mc);
44624f1eaadSschwarze 		p->flags &= ~(TERMP_NOBUF | TERMP_NEWMC);
44724f1eaadSschwarze 	}
4486b4eabafSschwarze 	p->viscol = 0;
449771c54bcSschwarze 	p->minbl = 0;
45024f1eaadSschwarze 	(*p->endline)(p);
451f73abda9Skristaps }
452f73abda9Skristaps 
453f73abda9Skristaps /*
454f73abda9Skristaps  * A newline only breaks an existing line; it won't assert vertical
455f73abda9Skristaps  * space.  All data in the output buffer is flushed prior to the newline
456f73abda9Skristaps  * assertion.
457f73abda9Skristaps  */
458f73abda9Skristaps void
term_newln(struct termp * p)459f73abda9Skristaps term_newln(struct termp *p)
460f73abda9Skristaps {
461f73abda9Skristaps 	p->flags |= TERMP_NOSPACE;
4628afa4451Sschwarze 	if (p->tcol->lastcol || p->viscol)
463f73abda9Skristaps 		term_flushln(p);
46418bbf166Sschwarze 	p->tcol->taboff = 0;
465f73abda9Skristaps }
466f73abda9Skristaps 
467f73abda9Skristaps /*
468f73abda9Skristaps  * Asserts a vertical space (a full, empty line-break between lines).
469f73abda9Skristaps  * Note that if used twice, this will cause two blank spaces and so on.
470f73abda9Skristaps  * All data in the output buffer is flushed prior to the newline
471f73abda9Skristaps  * assertion.
472f73abda9Skristaps  */
473f73abda9Skristaps void
term_vspace(struct termp * p)474f73abda9Skristaps term_vspace(struct termp *p)
475f73abda9Skristaps {
476f73abda9Skristaps 
477f73abda9Skristaps 	term_newln(p);
47814fc54faSschwarze 	p->viscol = 0;
47924f1eaadSschwarze 	p->minbl = 0;
480ef8ef3ffSschwarze 	if (0 < p->skipvsp)
481ef8ef3ffSschwarze 		p->skipvsp--;
482ef8ef3ffSschwarze 	else
483f95d589eSschwarze 		(*p->endline)(p);
484f73abda9Skristaps }
485f73abda9Skristaps 
486c48a0735Sschwarze /* Swap current and previous font; for \fP and .ft P */
487fa70b73eSschwarze void
term_fontlast(struct termp * p)488fa70b73eSschwarze term_fontlast(struct termp *p)
489f73abda9Skristaps {
490fa70b73eSschwarze 	enum termfont	 f;
491f73abda9Skristaps 
492fa70b73eSschwarze 	f = p->fontl;
493fa70b73eSschwarze 	p->fontl = p->fontq[p->fonti];
494fa70b73eSschwarze 	p->fontq[p->fonti] = f;
495a6d58804Sschwarze }
496a6d58804Sschwarze 
497c48a0735Sschwarze /* Set font, save current, discard previous; for \f, .ft, .B etc. */
498fa70b73eSschwarze void
term_fontrepl(struct termp * p,enum termfont f)499fa70b73eSschwarze term_fontrepl(struct termp *p, enum termfont f)
500fa70b73eSschwarze {
501fa70b73eSschwarze 
502fa70b73eSschwarze 	p->fontl = p->fontq[p->fonti];
503fa70b73eSschwarze 	p->fontq[p->fonti] = f;
504a6d58804Sschwarze }
505a6d58804Sschwarze 
506c48a0735Sschwarze /* Set font, save previous. */
507fa70b73eSschwarze void
term_fontpush(struct termp * p,enum termfont f)508fa70b73eSschwarze term_fontpush(struct termp *p, enum termfont f)
509fa70b73eSschwarze {
510fa70b73eSschwarze 
511fa70b73eSschwarze 	p->fontl = p->fontq[p->fonti];
512c48a0735Sschwarze 	if (++p->fonti == p->fontsz) {
513c48a0735Sschwarze 		p->fontsz += 8;
514c48a0735Sschwarze 		p->fontq = mandoc_reallocarray(p->fontq,
5155d40c03bSschwarze 		    p->fontsz, sizeof(*p->fontq));
516c48a0735Sschwarze 	}
517c48a0735Sschwarze 	p->fontq[p->fonti] = f;
518a6d58804Sschwarze }
519f73abda9Skristaps 
520c48a0735Sschwarze /* Flush to make the saved pointer current again. */
521fa70b73eSschwarze void
term_fontpopq(struct termp * p,int i)522f29a1da1Sschwarze term_fontpopq(struct termp *p, int i)
523fa70b73eSschwarze {
524fa70b73eSschwarze 
525f29a1da1Sschwarze 	assert(i >= 0);
526f29a1da1Sschwarze 	if (p->fonti > i)
527f29a1da1Sschwarze 		p->fonti = i;
528a6d58804Sschwarze }
529a6d58804Sschwarze 
530c48a0735Sschwarze /* Pop one font off the stack. */
531fa70b73eSschwarze void
term_fontpop(struct termp * p)532fa70b73eSschwarze term_fontpop(struct termp *p)
533fa70b73eSschwarze {
534f73abda9Skristaps 
535fa70b73eSschwarze 	assert(p->fonti);
536fa70b73eSschwarze 	p->fonti--;
537f73abda9Skristaps }
538f73abda9Skristaps 
539f73abda9Skristaps /*
540f73abda9Skristaps  * Handle pwords, partial words, which may be either a single word or a
541f73abda9Skristaps  * phrase that cannot be broken down (such as a literal string).  This
542f73abda9Skristaps  * handles word styling.
543f73abda9Skristaps  */
544a6d58804Sschwarze void
term_word(struct termp * p,const char * word)545a6d58804Sschwarze term_word(struct termp *p, const char *word)
546f73abda9Skristaps {
5478e935ceaSschwarze 	struct roffsu	 su;
5481192b926Sschwarze 	const char	 nbrsp[2] = { ASCII_NBRSP, 0 };
549a5e11edeSschwarze 	const char	*seq, *cp;
550a5e11edeSschwarze 	int		 sz, uc;
551bef920b0Sschwarze 	size_t		 csz, lsz, ssz;
552a5e11edeSschwarze 	enum mandoc_esc	 esc;
553f73abda9Skristaps 
55424f1eaadSschwarze 	if ((p->flags & TERMP_NOBUF) == 0) {
55524f1eaadSschwarze 		if ((p->flags & TERMP_NOSPACE) == 0) {
55624f1eaadSschwarze 			if ((p->flags & TERMP_KEEP) == 0) {
557fa70b73eSschwarze 				bufferc(p, ' ');
55824f1eaadSschwarze 				if (p->flags & TERMP_SENTENCE)
559bc49dbe1Sschwarze 					bufferc(p, ' ');
5601eead9a6Sschwarze 			} else
5611eead9a6Sschwarze 				bufferc(p, ASCII_NBRSP);
562bc49dbe1Sschwarze 		}
56324f1eaadSschwarze 		if (p->flags & TERMP_PREKEEP)
56405c89e8fSschwarze 			p->flags |= TERMP_KEEP;
56524f1eaadSschwarze 		if (p->flags & TERMP_NONOSPACE)
5668cd724fbSschwarze 			p->flags |= TERMP_NOSPACE;
56724f1eaadSschwarze 		else
56824f1eaadSschwarze 			p->flags &= ~TERMP_NOSPACE;
56964c3401cSschwarze 		p->flags &= ~(TERMP_SENTENCE | TERMP_NONEWLINE);
5700489aabfSschwarze 		p->skipvsp = 0;
57124f1eaadSschwarze 	}
572bc49dbe1Sschwarze 
573a5e11edeSschwarze 	while ('\0' != *word) {
5747eac745dSschwarze 		if ('\\' != *word) {
5751192b926Sschwarze 			if (TERMP_NBRWORD & p->flags) {
5761192b926Sschwarze 				if (' ' == *word) {
5771192b926Sschwarze 					encode(p, nbrsp, 1);
5781192b926Sschwarze 					word++;
5791192b926Sschwarze 					continue;
5801192b926Sschwarze 				}
5811192b926Sschwarze 				ssz = strcspn(word, "\\ ");
5821192b926Sschwarze 			} else
5837eac745dSschwarze 				ssz = strcspn(word, "\\");
5847eac745dSschwarze 			encode(p, word, ssz);
5857eac745dSschwarze 			word += (int)ssz;
5867eac745dSschwarze 			continue;
5877eac745dSschwarze 		}
588fa70b73eSschwarze 
589a5e11edeSschwarze 		word++;
590a5e11edeSschwarze 		esc = mandoc_escape(&word, &seq, &sz);
591a5e11edeSschwarze 		switch (esc) {
59249aff9f8Sschwarze 		case ESCAPE_UNICODE:
593a5e11edeSschwarze 			uc = mchars_num2uc(seq + 1, sz - 1);
594a5e11edeSschwarze 			break;
59549aff9f8Sschwarze 		case ESCAPE_NUMBERED:
596e93f0bfbSschwarze 			uc = mchars_num2char(seq, sz);
597e0e72893Sschwarze 			if (uc >= 0)
598a5e11edeSschwarze 				break;
599e0e72893Sschwarze 			bufferc(p, ASCII_NBRZW);
600e0e72893Sschwarze 			continue;
60149aff9f8Sschwarze 		case ESCAPE_SPECIAL:
60255f79d48Sschwarze 			if (p->enc == TERMENC_ASCII) {
60316536faaSschwarze 				cp = mchars_spec2str(seq, sz, &ssz);
6046899db98Sschwarze 				if (cp != NULL)
605a5e11edeSschwarze 					encode(p, cp, ssz);
606e0e72893Sschwarze 				else
607e0e72893Sschwarze 					bufferc(p, ASCII_NBRZW);
60855f79d48Sschwarze 			} else {
60916536faaSschwarze 				uc = mchars_spec2cp(seq, sz);
610dc90d003Sschwarze 				if (uc > 0)
61155f79d48Sschwarze 					encode1(p, uc);
612e0e72893Sschwarze 				else
613e0e72893Sschwarze 					bufferc(p, ASCII_NBRZW);
61455f79d48Sschwarze 			}
615e93f0bfbSschwarze 			continue;
6166f6722cbSschwarze 		case ESCAPE_UNDEF:
6176f6722cbSschwarze 			uc = *seq;
6186f6722cbSschwarze 			break;
61949aff9f8Sschwarze 		case ESCAPE_FONTBOLD:
6207d063611Sschwarze 		case ESCAPE_FONTCB:
621a5e11edeSschwarze 			term_fontrepl(p, TERMFONT_BOLD);
622e93f0bfbSschwarze 			continue;
62349aff9f8Sschwarze 		case ESCAPE_FONTITALIC:
6247d063611Sschwarze 		case ESCAPE_FONTCI:
625a5e11edeSschwarze 			term_fontrepl(p, TERMFONT_UNDER);
626e93f0bfbSschwarze 			continue;
62749aff9f8Sschwarze 		case ESCAPE_FONTBI:
62868941ea9Sschwarze 			term_fontrepl(p, TERMFONT_BI);
629e93f0bfbSschwarze 			continue;
63049aff9f8Sschwarze 		case ESCAPE_FONT:
6317d063611Sschwarze 		case ESCAPE_FONTCR:
63249aff9f8Sschwarze 		case ESCAPE_FONTROMAN:
633a5e11edeSschwarze 			term_fontrepl(p, TERMFONT_NONE);
634e93f0bfbSschwarze 			continue;
63549aff9f8Sschwarze 		case ESCAPE_FONTPREV:
636a5e11edeSschwarze 			term_fontlast(p);
637e93f0bfbSschwarze 			continue;
6386167ec38Sschwarze 		case ESCAPE_BREAK:
6396167ec38Sschwarze 			bufferc(p, '\n');
6406167ec38Sschwarze 			continue;
64149aff9f8Sschwarze 		case ESCAPE_NOSPACE:
642f2e60d14Sschwarze 			if (p->flags & TERMP_BACKAFTER)
643f2e60d14Sschwarze 				p->flags &= ~TERMP_BACKAFTER;
644f2e60d14Sschwarze 			else if (*word == '\0')
64564c3401cSschwarze 				p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE);
646e93f0bfbSschwarze 			continue;
6478138dde8Sschwarze 		case ESCAPE_DEVICE:
6488138dde8Sschwarze 			if (p->type == TERMTYPE_PDF)
6498138dde8Sschwarze 				encode(p, "pdf", 3);
6508138dde8Sschwarze 			else if (p->type == TERMTYPE_PS)
6518138dde8Sschwarze 				encode(p, "ps", 2);
6528138dde8Sschwarze 			else if (p->enc == TERMENC_ASCII)
6538138dde8Sschwarze 				encode(p, "ascii", 5);
6548138dde8Sschwarze 			else
6558138dde8Sschwarze 				encode(p, "utf8", 4);
6568138dde8Sschwarze 			continue;
6578e935ceaSschwarze 		case ESCAPE_HORIZ:
658a6e3a6a9Sschwarze 			if (p->flags & TERMP_BACKAFTER) {
659a6e3a6a9Sschwarze 				p->flags &= ~TERMP_BACKAFTER;
660a6e3a6a9Sschwarze 				continue;
661a6e3a6a9Sschwarze 			}
6622aa67a98Sschwarze 			if (*seq == '|') {
6632aa67a98Sschwarze 				seq++;
6642aa67a98Sschwarze 				uc = -p->col;
6652aa67a98Sschwarze 			} else
6662aa67a98Sschwarze 				uc = 0;
667ecd22486Sschwarze 			if (a2roffsu(seq, &su, SCALE_EM) == NULL)
6688e935ceaSschwarze 				continue;
6692aa67a98Sschwarze 			uc += term_hen(p, &su);
670a6e3a6a9Sschwarze 			if (uc >= 0) {
6716c5a416aSschwarze 				while (uc > 0) {
6726c5a416aSschwarze 					uc -= term_len(p, 1);
673a6e3a6a9Sschwarze 					if (p->flags & TERMP_BACKBEFORE)
674a6e3a6a9Sschwarze 						p->flags &= ~TERMP_BACKBEFORE;
675a6e3a6a9Sschwarze 					else
676a6e3a6a9Sschwarze 						bufferc(p, ASCII_NBRSP);
6776c5a416aSschwarze 				}
678a6e3a6a9Sschwarze 				continue;
679a6e3a6a9Sschwarze 			}
680a6e3a6a9Sschwarze 			if (p->flags & TERMP_BACKBEFORE) {
681a6e3a6a9Sschwarze 				p->flags &= ~TERMP_BACKBEFORE;
682a6e3a6a9Sschwarze 				assert(p->col > 0);
683a6e3a6a9Sschwarze 				p->col--;
684a6e3a6a9Sschwarze 			}
685a6e3a6a9Sschwarze 			if (p->col >= (size_t)(-uc)) {
6868e935ceaSschwarze 				p->col += uc;
6876c5a416aSschwarze 			} else {
6888e935ceaSschwarze 				uc += p->col;
6898e935ceaSschwarze 				p->col = 0;
690e93ea447Sschwarze 				if (p->tcol->offset > (size_t)(-uc)) {
6918e935ceaSschwarze 					p->ti += uc;
692e93ea447Sschwarze 					p->tcol->offset += uc;
6938e935ceaSschwarze 				} else {
694e93ea447Sschwarze 					p->ti -= p->tcol->offset;
695e93ea447Sschwarze 					p->tcol->offset = 0;
6968e935ceaSschwarze 				}
6978e935ceaSschwarze 			}
6988e935ceaSschwarze 			continue;
699bef920b0Sschwarze 		case ESCAPE_HLINE:
70023b9ae24Sschwarze 			if ((cp = a2roffsu(seq, &su, SCALE_EM)) == NULL)
701bef920b0Sschwarze 				continue;
702f4692b45Sschwarze 			uc = term_hen(p, &su);
703bef920b0Sschwarze 			if (uc <= 0) {
704e93ea447Sschwarze 				if (p->tcol->rmargin <= p->tcol->offset)
705bef920b0Sschwarze 					continue;
706e93ea447Sschwarze 				lsz = p->tcol->rmargin - p->tcol->offset;
707bef920b0Sschwarze 			} else
708bef920b0Sschwarze 				lsz = uc;
70923b9ae24Sschwarze 			if (*cp == seq[-1])
710bef920b0Sschwarze 				uc = -1;
71123b9ae24Sschwarze 			else if (*cp == '\\') {
71223b9ae24Sschwarze 				seq = cp + 1;
713bef920b0Sschwarze 				esc = mandoc_escape(&seq, &cp, &sz);
714bef920b0Sschwarze 				switch (esc) {
715bef920b0Sschwarze 				case ESCAPE_UNICODE:
716bef920b0Sschwarze 					uc = mchars_num2uc(cp + 1, sz - 1);
717bef920b0Sschwarze 					break;
718bef920b0Sschwarze 				case ESCAPE_NUMBERED:
719bef920b0Sschwarze 					uc = mchars_num2char(cp, sz);
720bef920b0Sschwarze 					break;
721bef920b0Sschwarze 				case ESCAPE_SPECIAL:
722bef920b0Sschwarze 					uc = mchars_spec2cp(cp, sz);
723bef920b0Sschwarze 					break;
7246f6722cbSschwarze 				case ESCAPE_UNDEF:
7256f6722cbSschwarze 					uc = *seq;
7266f6722cbSschwarze 					break;
727bef920b0Sschwarze 				default:
728bef920b0Sschwarze 					uc = -1;
729bef920b0Sschwarze 					break;
730bef920b0Sschwarze 				}
731bef920b0Sschwarze 			} else
73223b9ae24Sschwarze 				uc = *cp;
733bef920b0Sschwarze 			if (uc < 0x20 || (uc > 0x7E && uc < 0xA0))
734bef920b0Sschwarze 				uc = '_';
735bef920b0Sschwarze 			if (p->enc == TERMENC_ASCII) {
736bef920b0Sschwarze 				cp = ascii_uc2str(uc);
737bef920b0Sschwarze 				csz = term_strlen(p, cp);
738bef920b0Sschwarze 				ssz = strlen(cp);
739bef920b0Sschwarze 			} else
740bef920b0Sschwarze 				csz = (*p->width)(p, uc);
741bef920b0Sschwarze 			while (lsz >= csz) {
742bef920b0Sschwarze 				if (p->enc == TERMENC_ASCII)
743bef920b0Sschwarze 					encode(p, cp, ssz);
744bef920b0Sschwarze 				else
745bef920b0Sschwarze 					encode1(p, uc);
746bef920b0Sschwarze 				lsz -= csz;
747bef920b0Sschwarze 			}
748bef920b0Sschwarze 			continue;
74949aff9f8Sschwarze 		case ESCAPE_SKIPCHAR:
750f2e60d14Sschwarze 			p->flags |= TERMP_BACKAFTER;
751e93f0bfbSschwarze 			continue;
75264f4916cSschwarze 		case ESCAPE_OVERSTRIKE:
75364f4916cSschwarze 			cp = seq + sz;
75464f4916cSschwarze 			while (seq < cp) {
75564f4916cSschwarze 				if (*seq == '\\') {
75664f4916cSschwarze 					mandoc_escape(&seq, NULL, NULL);
75764f4916cSschwarze 					continue;
75864f4916cSschwarze 				}
75964f4916cSschwarze 				encode1(p, *seq++);
760f2e60d14Sschwarze 				if (seq < cp) {
761f2e60d14Sschwarze 					if (p->flags & TERMP_BACKBEFORE)
762f2e60d14Sschwarze 						p->flags |= TERMP_BACKAFTER;
763f2e60d14Sschwarze 					else
764f2e60d14Sschwarze 						p->flags |= TERMP_BACKBEFORE;
76564f4916cSschwarze 				}
766f2e60d14Sschwarze 			}
767c119144aSschwarze 			/* Trim trailing backspace/blank pair. */
7688afa4451Sschwarze 			if (p->tcol->lastcol > 2 &&
7698afa4451Sschwarze 			    (p->tcol->buf[p->tcol->lastcol - 1] == ' ' ||
7708afa4451Sschwarze 			     p->tcol->buf[p->tcol->lastcol - 1] == '\t'))
7718afa4451Sschwarze 				p->tcol->lastcol -= 2;
7728afa4451Sschwarze 			if (p->col > p->tcol->lastcol)
7738afa4451Sschwarze 				p->col = p->tcol->lastcol;
774f2e60d14Sschwarze 			continue;
775e0e72893Sschwarze 		case ESCAPE_IGNORE:
776e0e72893Sschwarze 			bufferc(p, ASCII_NBRZW);
777e0e72893Sschwarze 			continue;
778a5e11edeSschwarze 		default:
779e93f0bfbSschwarze 			continue;
780e93f0bfbSschwarze 		}
781e93f0bfbSschwarze 
782e93f0bfbSschwarze 		/*
783e93f0bfbSschwarze 		 * Common handling for Unicode and numbered
784e93f0bfbSschwarze 		 * character escape sequences.
785e93f0bfbSschwarze 		 */
786e93f0bfbSschwarze 
787e93f0bfbSschwarze 		if (p->enc == TERMENC_ASCII) {
788e93f0bfbSschwarze 			cp = ascii_uc2str(uc);
789e93f0bfbSschwarze 			encode(p, cp, strlen(cp));
790e93f0bfbSschwarze 		} else {
791e93f0bfbSschwarze 			if ((uc < 0x20 && uc != 0x09) ||
792e93f0bfbSschwarze 			    (uc > 0x7E && uc < 0xA0))
793e93f0bfbSschwarze 				uc = 0xFFFD;
794e93f0bfbSschwarze 			encode1(p, uc);
795fa70b73eSschwarze 		}
796f73abda9Skristaps 	}
7971192b926Sschwarze 	p->flags &= ~TERMP_NBRWORD;
798a5e11edeSschwarze }
799f73abda9Skristaps 
800f73abda9Skristaps static void
adjbuf(struct termp_col * c,size_t sz)801e93ea447Sschwarze adjbuf(struct termp_col *c, size_t sz)
802f73abda9Skristaps {
803e93ea447Sschwarze 	if (c->maxcols == 0)
804e93ea447Sschwarze 		c->maxcols = 1024;
805e93ea447Sschwarze 	while (c->maxcols <= sz)
806e93ea447Sschwarze 		c->maxcols <<= 2;
807e93ea447Sschwarze 	c->buf = mandoc_reallocarray(c->buf, c->maxcols, sizeof(*c->buf));
808f73abda9Skristaps }
809f73abda9Skristaps 
8104c45d6c2Sschwarze static void
bufferc(struct termp * p,char c)811fa70b73eSschwarze bufferc(struct termp *p, char c)
812fa70b73eSschwarze {
81324f1eaadSschwarze 	if (p->flags & TERMP_NOBUF) {
81424f1eaadSschwarze 		(*p->letter)(p, c);
81524f1eaadSschwarze 		return;
81624f1eaadSschwarze 	}
817e93ea447Sschwarze 	if (p->col + 1 >= p->tcol->maxcols)
818e93ea447Sschwarze 		adjbuf(p->tcol, p->col + 1);
8198afa4451Sschwarze 	if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
820e93ea447Sschwarze 		p->tcol->buf[p->col] = c;
8218afa4451Sschwarze 	if (p->tcol->lastcol < ++p->col)
8228afa4451Sschwarze 		p->tcol->lastcol = p->col;
823b822ca0dSschwarze }
824fa70b73eSschwarze 
82518bbf166Sschwarze void
term_tab_ref(struct termp * p)82618bbf166Sschwarze term_tab_ref(struct termp *p)
82718bbf166Sschwarze {
82818bbf166Sschwarze 	if (p->tcol->lastcol && p->tcol->lastcol <= p->col &&
82918bbf166Sschwarze 	    (p->flags & TERMP_NOBUF) == 0)
83018bbf166Sschwarze 		bufferc(p, ASCII_TABREF);
83118bbf166Sschwarze }
83218bbf166Sschwarze 
833a5e11edeSschwarze /*
834a5e11edeSschwarze  * See encode().
835a5e11edeSschwarze  * Do this for a single (probably unicode) value.
836a5e11edeSschwarze  * Does not check for non-decorated glyphs.
837a5e11edeSschwarze  */
838a5e11edeSschwarze static void
encode1(struct termp * p,int c)839a5e11edeSschwarze encode1(struct termp *p, int c)
840a5e11edeSschwarze {
841a5e11edeSschwarze 	enum termfont	  f;
842a5e11edeSschwarze 
84324f1eaadSschwarze 	if (p->flags & TERMP_NOBUF) {
84424f1eaadSschwarze 		(*p->letter)(p, c);
84524f1eaadSschwarze 		return;
84624f1eaadSschwarze 	}
84724f1eaadSschwarze 
848e93ea447Sschwarze 	if (p->col + 7 >= p->tcol->maxcols)
849e93ea447Sschwarze 		adjbuf(p->tcol, p->col + 7);
850f2e60d14Sschwarze 
85163edb084Sschwarze 	f = (c == ASCII_HYPH || c > 127 || isgraph(c)) ?
852f2e60d14Sschwarze 	    p->fontq[p->fonti] : TERMFONT_NONE;
853f2e60d14Sschwarze 
854f2e60d14Sschwarze 	if (p->flags & TERMP_BACKBEFORE) {
855e93ea447Sschwarze 		if (p->tcol->buf[p->col - 1] == ' ' ||
856e93ea447Sschwarze 		    p->tcol->buf[p->col - 1] == '\t')
857c119144aSschwarze 			p->col--;
858c119144aSschwarze 		else
859e93ea447Sschwarze 			p->tcol->buf[p->col++] = '\b';
860f2e60d14Sschwarze 		p->flags &= ~TERMP_BACKBEFORE;
8617eac745dSschwarze 	}
862e93ea447Sschwarze 	if (f == TERMFONT_UNDER || f == TERMFONT_BI) {
863e93ea447Sschwarze 		p->tcol->buf[p->col++] = '_';
864e93ea447Sschwarze 		p->tcol->buf[p->col++] = '\b';
86568941ea9Sschwarze 	}
866e93ea447Sschwarze 	if (f == TERMFONT_BOLD || f == TERMFONT_BI) {
867e93ea447Sschwarze 		if (c == ASCII_HYPH)
868e93ea447Sschwarze 			p->tcol->buf[p->col++] = '-';
86968941ea9Sschwarze 		else
870e93ea447Sschwarze 			p->tcol->buf[p->col++] = c;
871e93ea447Sschwarze 		p->tcol->buf[p->col++] = '\b';
87268941ea9Sschwarze 	}
8738afa4451Sschwarze 	if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
874e93ea447Sschwarze 		p->tcol->buf[p->col] = c;
8758afa4451Sschwarze 	if (p->tcol->lastcol < ++p->col)
8768afa4451Sschwarze 		p->tcol->lastcol = p->col;
877f2e60d14Sschwarze 	if (p->flags & TERMP_BACKAFTER) {
878f2e60d14Sschwarze 		p->flags |= TERMP_BACKBEFORE;
879f2e60d14Sschwarze 		p->flags &= ~TERMP_BACKAFTER;
880f2e60d14Sschwarze 	}
881a5e11edeSschwarze }
882fa70b73eSschwarze 
883fa70b73eSschwarze static void
encode(struct termp * p,const char * word,size_t sz)884fa70b73eSschwarze encode(struct termp *p, const char *word, size_t sz)
885fa70b73eSschwarze {
8864e2bfd2fSschwarze 	size_t		  i;
887a5e11edeSschwarze 
88824f1eaadSschwarze 	if (p->flags & TERMP_NOBUF) {
88924f1eaadSschwarze 		for (i = 0; i < sz; i++)
89024f1eaadSschwarze 			(*p->letter)(p, word[i]);
89124f1eaadSschwarze 		return;
89224f1eaadSschwarze 	}
89324f1eaadSschwarze 
894e93ea447Sschwarze 	if (p->col + 2 + (sz * 5) >= p->tcol->maxcols)
895e93ea447Sschwarze 		adjbuf(p->tcol, p->col + 2 + (sz * 5));
8968cd724fbSschwarze 
8974e2bfd2fSschwarze 	for (i = 0; i < sz; i++) {
89868941ea9Sschwarze 		if (ASCII_HYPH == word[i] ||
89968941ea9Sschwarze 		    isgraph((unsigned char)word[i]))
90068941ea9Sschwarze 			encode1(p, word[i]);
901e5f88505Sschwarze 		else {
9028afa4451Sschwarze 			if (p->tcol->lastcol <= p->col ||
903108ab9ecSschwarze 			    (word[i] != ' ' && word[i] != ASCII_NBRSP))
904e93ea447Sschwarze 				p->tcol->buf[p->col] = word[i];
905108ab9ecSschwarze 			p->col++;
906e5f88505Sschwarze 
907e5f88505Sschwarze 			/*
908e5f88505Sschwarze 			 * Postpone the effect of \z while handling
909e5f88505Sschwarze 			 * an overstrike sequence from ascii_uc2str().
910e5f88505Sschwarze 			 */
911e5f88505Sschwarze 
912e5f88505Sschwarze 			if (word[i] == '\b' &&
913e5f88505Sschwarze 			    (p->flags & TERMP_BACKBEFORE)) {
914e5f88505Sschwarze 				p->flags &= ~TERMP_BACKBEFORE;
915e5f88505Sschwarze 				p->flags |= TERMP_BACKAFTER;
916e5f88505Sschwarze 			}
917e5f88505Sschwarze 		}
918fa70b73eSschwarze 	}
9198afa4451Sschwarze 	if (p->tcol->lastcol < p->col)
9208afa4451Sschwarze 		p->tcol->lastcol = p->col;
9214c45d6c2Sschwarze }
9224175bdabSschwarze 
92310111bf6Sschwarze void
term_setwidth(struct termp * p,const char * wstr)92410111bf6Sschwarze term_setwidth(struct termp *p, const char *wstr)
92510111bf6Sschwarze {
92610111bf6Sschwarze 	struct roffsu	 su;
92713a35416Sschwarze 	int		 iop, width;
92810111bf6Sschwarze 
929254fd162Sschwarze 	iop = 0;
930254fd162Sschwarze 	width = 0;
93110111bf6Sschwarze 	if (NULL != wstr) {
93210111bf6Sschwarze 		switch (*wstr) {
93349aff9f8Sschwarze 		case '+':
93410111bf6Sschwarze 			iop = 1;
93510111bf6Sschwarze 			wstr++;
93610111bf6Sschwarze 			break;
93749aff9f8Sschwarze 		case '-':
93810111bf6Sschwarze 			iop = -1;
93910111bf6Sschwarze 			wstr++;
94010111bf6Sschwarze 			break;
94110111bf6Sschwarze 		default:
94210111bf6Sschwarze 			break;
94310111bf6Sschwarze 		}
944ecd22486Sschwarze 		if (a2roffsu(wstr, &su, SCALE_MAX) != NULL)
945254fd162Sschwarze 			width = term_hspan(p, &su);
946254fd162Sschwarze 		else
94710111bf6Sschwarze 			iop = 0;
94810111bf6Sschwarze 	}
94910111bf6Sschwarze 	(*p->setwidth)(p, iop, width);
95010111bf6Sschwarze }
95110111bf6Sschwarze 
9524175bdabSschwarze size_t
term_len(const struct termp * p,size_t sz)9533ebeb861Sschwarze term_len(const struct termp *p, size_t sz)
9543ebeb861Sschwarze {
9553ebeb861Sschwarze 
956526e306bSschwarze 	return (*p->width)(p, ' ') * sz;
9573ebeb861Sschwarze }
9583ebeb861Sschwarze 
9597eac745dSschwarze static size_t
cond_width(const struct termp * p,int c,int * skip)9607eac745dSschwarze cond_width(const struct termp *p, int c, int *skip)
9617eac745dSschwarze {
9627eac745dSschwarze 
9637eac745dSschwarze 	if (*skip) {
9647eac745dSschwarze 		(*skip) = 0;
965526e306bSschwarze 		return 0;
9667eac745dSschwarze 	} else
967526e306bSschwarze 		return (*p->width)(p, c);
9687eac745dSschwarze }
9693ebeb861Sschwarze 
9703ebeb861Sschwarze size_t
term_strlen(const struct termp * p,const char * cp)9713ebeb861Sschwarze term_strlen(const struct termp *p, const char *cp)
9723ebeb861Sschwarze {
973a5e11edeSschwarze 	size_t		 sz, rsz, i;
974e93f0bfbSschwarze 	int		 ssz, skip, uc;
9757586f75aSschwarze 	const char	*seq, *rhs;
976a5e11edeSschwarze 	enum mandoc_esc	 esc;
977e0e72893Sschwarze 	static const char rej[] = { '\\', ASCII_NBRSP, ASCII_NBRZW,
97818bbf166Sschwarze 		ASCII_BREAK, ASCII_HYPH, ASCII_TABREF, '\0' };
9793ebeb861Sschwarze 
9807586f75aSschwarze 	/*
9817586f75aSschwarze 	 * Account for escaped sequences within string length
982a5e11edeSschwarze 	 * calculations.  This follows the logic in term_word() as we
983a5e11edeSschwarze 	 * must calculate the width of produced strings.
9847586f75aSschwarze 	 */
9857586f75aSschwarze 
986a5e11edeSschwarze 	sz = 0;
9877eac745dSschwarze 	skip = 0;
988a5e11edeSschwarze 	while ('\0' != *cp) {
989a5e11edeSschwarze 		rsz = strcspn(cp, rej);
990a5e11edeSschwarze 		for (i = 0; i < rsz; i++)
9917eac745dSschwarze 			sz += cond_width(p, *cp++, &skip);
992a5e11edeSschwarze 
993a5e11edeSschwarze 		switch (*cp) {
99449aff9f8Sschwarze 		case '\\':
995a5e11edeSschwarze 			cp++;
996a5e11edeSschwarze 			rhs = NULL;
9976f6722cbSschwarze 			esc = mandoc_escape(&cp, &seq, &ssz);
998a5e11edeSschwarze 			switch (esc) {
99949aff9f8Sschwarze 			case ESCAPE_UNICODE:
1000064c012dSschwarze 				uc = mchars_num2uc(seq + 1, ssz - 1);
1001a5e11edeSschwarze 				break;
100249aff9f8Sschwarze 			case ESCAPE_NUMBERED:
1003e93f0bfbSschwarze 				uc = mchars_num2char(seq, ssz);
1004e93f0bfbSschwarze 				if (uc < 0)
1005e93f0bfbSschwarze 					continue;
1006a5e11edeSschwarze 				break;
100749aff9f8Sschwarze 			case ESCAPE_SPECIAL:
1008e93f0bfbSschwarze 				if (p->enc == TERMENC_ASCII) {
100916536faaSschwarze 					rhs = mchars_spec2str(seq, ssz, &rsz);
1010e93f0bfbSschwarze 					if (rhs != NULL)
10117586f75aSschwarze 						break;
1012e93f0bfbSschwarze 				} else {
101316536faaSschwarze 					uc = mchars_spec2cp(seq, ssz);
1014e93f0bfbSschwarze 					if (uc > 0)
1015e93f0bfbSschwarze 						sz += cond_width(p, uc, &skip);
1016e93f0bfbSschwarze 				}
1017e93f0bfbSschwarze 				continue;
10186f6722cbSschwarze 			case ESCAPE_UNDEF:
10196f6722cbSschwarze 				uc = *seq;
10206f6722cbSschwarze 				break;
10218138dde8Sschwarze 			case ESCAPE_DEVICE:
10228138dde8Sschwarze 				if (p->type == TERMTYPE_PDF) {
10238138dde8Sschwarze 					rhs = "pdf";
10248138dde8Sschwarze 					rsz = 3;
10258138dde8Sschwarze 				} else if (p->type == TERMTYPE_PS) {
10268138dde8Sschwarze 					rhs = "ps";
10278138dde8Sschwarze 					rsz = 2;
10288138dde8Sschwarze 				} else if (p->enc == TERMENC_ASCII) {
10298138dde8Sschwarze 					rhs = "ascii";
10308138dde8Sschwarze 					rsz = 5;
10318138dde8Sschwarze 				} else {
10328138dde8Sschwarze 					rhs = "utf8";
10338138dde8Sschwarze 					rsz = 4;
10348138dde8Sschwarze 				}
10358138dde8Sschwarze 				break;
103649aff9f8Sschwarze 			case ESCAPE_SKIPCHAR:
10377eac745dSschwarze 				skip = 1;
1038e93f0bfbSschwarze 				continue;
103964f4916cSschwarze 			case ESCAPE_OVERSTRIKE:
104064f4916cSschwarze 				rsz = 0;
104164f4916cSschwarze 				rhs = seq + ssz;
104264f4916cSschwarze 				while (seq < rhs) {
104364f4916cSschwarze 					if (*seq == '\\') {
104464f4916cSschwarze 						mandoc_escape(&seq, NULL, NULL);
104564f4916cSschwarze 						continue;
104664f4916cSschwarze 					}
104764f4916cSschwarze 					i = (*p->width)(p, *seq++);
104864f4916cSschwarze 					if (rsz < i)
104964f4916cSschwarze 						rsz = i;
105064f4916cSschwarze 				}
105164f4916cSschwarze 				sz += rsz;
105264f4916cSschwarze 				continue;
10537586f75aSschwarze 			default:
1054e93f0bfbSschwarze 				continue;
10557586f75aSschwarze 			}
10567586f75aSschwarze 
1057e93f0bfbSschwarze 			/*
1058e93f0bfbSschwarze 			 * Common handling for Unicode and numbered
1059e93f0bfbSschwarze 			 * character escape sequences.
1060e93f0bfbSschwarze 			 */
1061e93f0bfbSschwarze 
1062e93f0bfbSschwarze 			if (rhs == NULL) {
1063e93f0bfbSschwarze 				if (p->enc == TERMENC_ASCII) {
1064e93f0bfbSschwarze 					rhs = ascii_uc2str(uc);
1065e93f0bfbSschwarze 					rsz = strlen(rhs);
1066e93f0bfbSschwarze 				} else {
1067e93f0bfbSschwarze 					if ((uc < 0x20 && uc != 0x09) ||
1068e93f0bfbSschwarze 					    (uc > 0x7E && uc < 0xA0))
1069e93f0bfbSschwarze 						uc = 0xFFFD;
1070e93f0bfbSschwarze 					sz += cond_width(p, uc, &skip);
1071e93f0bfbSschwarze 					continue;
1072e93f0bfbSschwarze 				}
1073e93f0bfbSschwarze 			}
1074a5e11edeSschwarze 
10757eac745dSschwarze 			if (skip) {
10767eac745dSschwarze 				skip = 0;
10777eac745dSschwarze 				break;
10787eac745dSschwarze 			}
10797eac745dSschwarze 
1080e93f0bfbSschwarze 			/*
1081e93f0bfbSschwarze 			 * Common handling for all escape sequences
1082e93f0bfbSschwarze 			 * printing more than one character.
1083e93f0bfbSschwarze 			 */
1084e93f0bfbSschwarze 
10857586f75aSschwarze 			for (i = 0; i < rsz; i++)
10867586f75aSschwarze 				sz += (*p->width)(p, *rhs++);
1087a5e11edeSschwarze 			break;
108849aff9f8Sschwarze 		case ASCII_NBRSP:
10897eac745dSschwarze 			sz += cond_width(p, ' ', &skip);
10902791bd1cSschwarze 			cp++;
1091a5e11edeSschwarze 			break;
109249aff9f8Sschwarze 		case ASCII_HYPH:
10937eac745dSschwarze 			sz += cond_width(p, '-', &skip);
10942791bd1cSschwarze 			cp++;
1095a5e11edeSschwarze 			break;
1096a5e11edeSschwarze 		default:
1097a5e11edeSschwarze 			break;
1098a5e11edeSschwarze 		}
1099a5e11edeSschwarze 	}
11003ebeb861Sschwarze 
1101526e306bSschwarze 	return sz;
11023ebeb861Sschwarze }
11033ebeb861Sschwarze 
1104bf8e53c9Sschwarze int
term_vspan(const struct termp * p,const struct roffsu * su)11053ebeb861Sschwarze term_vspan(const struct termp *p, const struct roffsu *su)
11064175bdabSschwarze {
11074175bdabSschwarze 	double		 r;
11089327b421Sschwarze 	int		 ri;
11094175bdabSschwarze 
11104175bdabSschwarze 	switch (su->unit) {
111182de3b2cSschwarze 	case SCALE_BU:
111282de3b2cSschwarze 		r = su->scale / 40.0;
111382de3b2cSschwarze 		break;
111449aff9f8Sschwarze 	case SCALE_CM:
111582de3b2cSschwarze 		r = su->scale * 6.0 / 2.54;
111682de3b2cSschwarze 		break;
111782de3b2cSschwarze 	case SCALE_FS:
111882de3b2cSschwarze 		r = su->scale * 65536.0 / 40.0;
11194175bdabSschwarze 		break;
112049aff9f8Sschwarze 	case SCALE_IN:
112153ab8734Sschwarze 		r = su->scale * 6.0;
11224175bdabSschwarze 		break;
112382de3b2cSschwarze 	case SCALE_MM:
112482de3b2cSschwarze 		r = su->scale * 0.006;
112582de3b2cSschwarze 		break;
112649aff9f8Sschwarze 	case SCALE_PC:
11274175bdabSschwarze 		r = su->scale;
11284175bdabSschwarze 		break;
112949aff9f8Sschwarze 	case SCALE_PT:
113082de3b2cSschwarze 		r = su->scale / 12.0;
11314175bdabSschwarze 		break;
113282de3b2cSschwarze 	case SCALE_EN:
113382de3b2cSschwarze 	case SCALE_EM:
113482de3b2cSschwarze 		r = su->scale * 0.6;
11354175bdabSschwarze 		break;
113649aff9f8Sschwarze 	case SCALE_VS:
11374175bdabSschwarze 		r = su->scale;
11384175bdabSschwarze 		break;
11394175bdabSschwarze 	default:
114082de3b2cSschwarze 		abort();
11414175bdabSschwarze 	}
11429327b421Sschwarze 	ri = r > 0.0 ? r + 0.4995 : r - 0.4995;
1143526e306bSschwarze 	return ri < 66 ? ri : 1;
11444175bdabSschwarze }
11454175bdabSschwarze 
114613a35416Sschwarze /*
1147f4692b45Sschwarze  * Convert a scaling width to basic units, rounding towards 0.
114813a35416Sschwarze  */
1149bf8e53c9Sschwarze int
term_hspan(const struct termp * p,const struct roffsu * su)11503ebeb861Sschwarze term_hspan(const struct termp *p, const struct roffsu *su)
11514175bdabSschwarze {
11524175bdabSschwarze 
1153526e306bSschwarze 	return (*p->hspan)(p, su);
11544175bdabSschwarze }
1155f4692b45Sschwarze 
1156f4692b45Sschwarze /*
1157f4692b45Sschwarze  * Convert a scaling width to basic units, rounding to closest.
1158f4692b45Sschwarze  */
1159f4692b45Sschwarze int
term_hen(const struct termp * p,const struct roffsu * su)1160f4692b45Sschwarze term_hen(const struct termp *p, const struct roffsu *su)
1161f4692b45Sschwarze {
1162f4692b45Sschwarze 	int bu;
1163f4692b45Sschwarze 
1164f4692b45Sschwarze 	if ((bu = (*p->hspan)(p, su)) >= 0)
1165f4692b45Sschwarze 		return (bu + 11) / 24;
1166f4692b45Sschwarze 	else
1167f4692b45Sschwarze 		return -((-bu + 11) / 24);
1168f4692b45Sschwarze }
1169