xref: /illumos-gate/usr/src/cmd/mandoc/term.c (revision 4d131170e62381276a07ffc0aeb1b62e527d940c)
1*4d131170SRobert Mustacchi /* $Id: term.c,v 1.283 2021/08/10 12:55:04 schwarze Exp $ */
295c635efSGarrett D'Amore /*
395c635efSGarrett D'Amore  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4*4d131170SRobert Mustacchi  * Copyright (c) 2010-2020 Ingo Schwarze <schwarze@openbsd.org>
595c635efSGarrett D'Amore  *
695c635efSGarrett D'Amore  * Permission to use, copy, modify, and distribute this software for any
795c635efSGarrett D'Amore  * purpose with or without fee is hereby granted, provided that the above
895c635efSGarrett D'Amore  * copyright notice and this permission notice appear in all copies.
995c635efSGarrett D'Amore  *
10371584c2SYuri Pankov  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1195c635efSGarrett D'Amore  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12371584c2SYuri Pankov  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1395c635efSGarrett D'Amore  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1495c635efSGarrett D'Amore  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1595c635efSGarrett D'Amore  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1695c635efSGarrett D'Amore  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1795c635efSGarrett D'Amore  */
1895c635efSGarrett D'Amore #include "config.h"
1995c635efSGarrett D'Amore 
2095c635efSGarrett D'Amore #include <sys/types.h>
2195c635efSGarrett D'Amore 
2295c635efSGarrett D'Amore #include <assert.h>
2395c635efSGarrett D'Amore #include <ctype.h>
24cec8643bSMichal Nowak #include <stdint.h>
2595c635efSGarrett D'Amore #include <stdio.h>
2695c635efSGarrett D'Amore #include <stdlib.h>
2795c635efSGarrett D'Amore #include <string.h>
2895c635efSGarrett D'Amore 
2995c635efSGarrett D'Amore #include "mandoc.h"
30260e9a87SYuri Pankov #include "mandoc_aux.h"
3195c635efSGarrett D'Amore #include "out.h"
3295c635efSGarrett D'Amore #include "term.h"
3395c635efSGarrett D'Amore #include "main.h"
3495c635efSGarrett D'Amore 
35698f87a4SGarrett D'Amore static	size_t		 cond_width(const struct termp *, int, int *);
36c66b8046SYuri Pankov static	void		 adjbuf(struct termp_col *, size_t);
3795c635efSGarrett D'Amore static	void		 bufferc(struct termp *, char);
3895c635efSGarrett D'Amore static	void		 encode(struct termp *, const char *, size_t);
3995c635efSGarrett D'Amore static	void		 encode1(struct termp *, int);
40c66b8046SYuri Pankov static	void		 endline(struct termp *);
41*4d131170SRobert Mustacchi static	void		 term_field(struct termp *, size_t, size_t);
42cec8643bSMichal Nowak static	void		 term_fill(struct termp *, size_t *, size_t *,
43cec8643bSMichal Nowak 				size_t);
4495c635efSGarrett D'Amore 
45260e9a87SYuri Pankov 
4695c635efSGarrett D'Amore void
term_setcol(struct termp * p,size_t maxtcol)47c66b8046SYuri Pankov term_setcol(struct termp *p, size_t maxtcol)
48c66b8046SYuri Pankov {
49c66b8046SYuri Pankov 	if (maxtcol > p->maxtcol) {
50c66b8046SYuri Pankov 		p->tcols = mandoc_recallocarray(p->tcols,
51c66b8046SYuri Pankov 		    p->maxtcol, maxtcol, sizeof(*p->tcols));
52c66b8046SYuri Pankov 		p->maxtcol = maxtcol;
53c66b8046SYuri Pankov 	}
54c66b8046SYuri Pankov 	p->lasttcol = maxtcol - 1;
55c66b8046SYuri Pankov 	p->tcol = p->tcols;
56c66b8046SYuri Pankov }
57c66b8046SYuri Pankov 
58c66b8046SYuri Pankov void
term_free(struct termp * p)5995c635efSGarrett D'Amore term_free(struct termp *p)
6095c635efSGarrett D'Amore {
61c66b8046SYuri Pankov 	for (p->tcol = p->tcols; p->tcol < p->tcols + p->maxtcol; p->tcol++)
62c66b8046SYuri Pankov 		free(p->tcol->buf);
63c66b8046SYuri Pankov 	free(p->tcols);
64260e9a87SYuri Pankov 	free(p->fontq);
6595c635efSGarrett D'Amore 	free(p);
6695c635efSGarrett D'Amore }
6795c635efSGarrett D'Amore 
6895c635efSGarrett D'Amore void
term_begin(struct termp * p,term_margin head,term_margin foot,const struct roff_meta * arg)6995c635efSGarrett D'Amore term_begin(struct termp *p, term_margin head,
70371584c2SYuri Pankov 		term_margin foot, const struct roff_meta *arg)
7195c635efSGarrett D'Amore {
7295c635efSGarrett D'Amore 
7395c635efSGarrett D'Amore 	p->headf = head;
7495c635efSGarrett D'Amore 	p->footf = foot;
7595c635efSGarrett D'Amore 	p->argf = arg;
7695c635efSGarrett D'Amore 	(*p->begin)(p);
7795c635efSGarrett D'Amore }
7895c635efSGarrett D'Amore 
7995c635efSGarrett D'Amore void
term_end(struct termp * p)8095c635efSGarrett D'Amore term_end(struct termp *p)
8195c635efSGarrett D'Amore {
8295c635efSGarrett D'Amore 
8395c635efSGarrett D'Amore 	(*p->end)(p);
8495c635efSGarrett D'Amore }
8595c635efSGarrett D'Amore 
8695c635efSGarrett D'Amore /*
87260e9a87SYuri Pankov  * Flush a chunk of text.  By default, break the output line each time
88260e9a87SYuri Pankov  * the right margin is reached, and continue output on the next line
89260e9a87SYuri Pankov  * at the same offset as the chunk itself.  By default, also break the
90cec8643bSMichal Nowak  * output line at the end of the chunk.  There are many flags modifying
91cec8643bSMichal Nowak  * this behaviour, see the comments in the body of the function.
9295c635efSGarrett D'Amore  */
9395c635efSGarrett D'Amore void
term_flushln(struct termp * p)9495c635efSGarrett D'Amore term_flushln(struct termp *p)
9595c635efSGarrett D'Amore {
96cec8643bSMichal Nowak 	size_t	 vbl;      /* Number of blanks to prepend to the output. */
97cec8643bSMichal Nowak 	size_t	 vbr;      /* Actual visual position of the end of field. */
98cec8643bSMichal Nowak 	size_t	 vfield;   /* Desired visual field width. */
99cec8643bSMichal Nowak 	size_t	 vtarget;  /* Desired visual position of the right margin. */
100cec8643bSMichal Nowak 	size_t	 ic;       /* Character position in the input buffer. */
101cec8643bSMichal Nowak 	size_t	 nbr;      /* Number of characters to print in this field. */
102cec8643bSMichal Nowak 
103cec8643bSMichal Nowak 	/*
104cec8643bSMichal Nowak 	 * Normally, start writing at the left margin, but with the
105cec8643bSMichal Nowak 	 * NOPAD flag, start writing at the current position instead.
106cec8643bSMichal Nowak 	 */
10795c635efSGarrett D'Amore 
108c66b8046SYuri Pankov 	vbl = (p->flags & TERMP_NOPAD) || p->tcol->offset < p->viscol ?
109c66b8046SYuri Pankov 	    0 : p->tcol->offset - p->viscol;
110c66b8046SYuri Pankov 	if (p->minbl && vbl < p->minbl)
111c66b8046SYuri Pankov 		vbl = p->minbl;
11295c635efSGarrett D'Amore 
113c66b8046SYuri Pankov 	if ((p->flags & TERMP_MULTICOL) == 0)
114c66b8046SYuri Pankov 		p->tcol->col = 0;
115cec8643bSMichal Nowak 
116cec8643bSMichal Nowak 	/* Loop over output lines. */
117cec8643bSMichal Nowak 
118cec8643bSMichal Nowak 	for (;;) {
119cec8643bSMichal Nowak 		vfield = p->tcol->rmargin > p->viscol + vbl ?
120cec8643bSMichal Nowak 		    p->tcol->rmargin - p->viscol - vbl : 0;
121c66b8046SYuri Pankov 
12295c635efSGarrett D'Amore 		/*
123cec8643bSMichal Nowak 		 * Normally, break the line at the the right margin
124cec8643bSMichal Nowak 		 * of the field, but with the NOBREAK flag, only
125cec8643bSMichal Nowak 		 * break it at the max right margin of the screen,
126cec8643bSMichal Nowak 		 * and with the BRNEVER flag, never break it at all.
12795c635efSGarrett D'Amore 		 */
128c66b8046SYuri Pankov 
129*4d131170SRobert Mustacchi 		vtarget = (p->flags & TERMP_NOBREAK) == 0 ? vfield :
130cec8643bSMichal Nowak 		    p->maxrmargin > p->viscol + vbl ?
131cec8643bSMichal Nowak 		    p->maxrmargin - p->viscol - vbl : 0;
13295c635efSGarrett D'Amore 
13395c635efSGarrett D'Amore 		/*
134cec8643bSMichal Nowak 		 * Figure out how much text will fit in the field.
135cec8643bSMichal Nowak 		 * If there is whitespace only, print nothing.
13695c635efSGarrett D'Amore 		 */
13795c635efSGarrett D'Amore 
138*4d131170SRobert Mustacchi 		term_fill(p, &nbr, &vbr,
139*4d131170SRobert Mustacchi 		    p->flags & TERMP_BRNEVER ? SIZE_MAX : vtarget);
140cec8643bSMichal Nowak 		if (nbr == 0)
14195c635efSGarrett D'Amore 			break;
14295c635efSGarrett D'Amore 
143cec8643bSMichal Nowak 		/*
144cec8643bSMichal Nowak 		 * With the CENTER or RIGHT flag, increase the indentation
145cec8643bSMichal Nowak 		 * to center the text between the left and right margins
146cec8643bSMichal Nowak 		 * or to adjust it to the right margin, respectively.
147cec8643bSMichal Nowak 		 */
148cec8643bSMichal Nowak 
149cec8643bSMichal Nowak 		if (vbr < vtarget) {
150cec8643bSMichal Nowak 			if (p->flags & TERMP_CENTER)
151cec8643bSMichal Nowak 				vbl += (vtarget - vbr) / 2;
152cec8643bSMichal Nowak 			else if (p->flags & TERMP_RIGHT)
153cec8643bSMichal Nowak 				vbl += vtarget - vbr;
154cec8643bSMichal Nowak 		}
155cec8643bSMichal Nowak 
156cec8643bSMichal Nowak 		/* Finally, print the field content. */
157cec8643bSMichal Nowak 
158*4d131170SRobert Mustacchi 		term_field(p, vbl, nbr);
159cec8643bSMichal Nowak 
160cec8643bSMichal Nowak 		/*
161cec8643bSMichal Nowak 		 * If there is no text left in the field, exit the loop.
162cec8643bSMichal Nowak 		 * If the BRTRSP flag is set, consider trailing
163cec8643bSMichal Nowak 		 * whitespace significant when deciding whether
164cec8643bSMichal Nowak 		 * the field fits or not.
165cec8643bSMichal Nowak 		 */
166cec8643bSMichal Nowak 
167cec8643bSMichal Nowak 		for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
168cec8643bSMichal Nowak 			switch (p->tcol->buf[ic]) {
169cec8643bSMichal Nowak 			case '\t':
170cec8643bSMichal Nowak 				if (p->flags & TERMP_BRTRSP)
171cec8643bSMichal Nowak 					vbr = term_tab_next(vbr);
17295c635efSGarrett D'Amore 				continue;
173cec8643bSMichal Nowak 			case ' ':
174cec8643bSMichal Nowak 				if (p->flags & TERMP_BRTRSP)
175cec8643bSMichal Nowak 					vbr += (*p->width)(p, ' ');
176c66b8046SYuri Pankov 				continue;
177cec8643bSMichal Nowak 			case '\n':
178cec8643bSMichal Nowak 			case ASCII_BREAK:
179cec8643bSMichal Nowak 				continue;
180cec8643bSMichal Nowak 			default:
18195c635efSGarrett D'Amore 				break;
182cec8643bSMichal Nowak 			}
183cec8643bSMichal Nowak 			break;
184cec8643bSMichal Nowak 		}
185cec8643bSMichal Nowak 		if (ic == p->tcol->lastcol)
186cec8643bSMichal Nowak 			break;
187cec8643bSMichal Nowak 
188cec8643bSMichal Nowak 		/*
189cec8643bSMichal Nowak 		 * At the location of an automtic line break, input
190cec8643bSMichal Nowak 		 * space characters are consumed by the line break.
191cec8643bSMichal Nowak 		 */
192cec8643bSMichal Nowak 
193c66b8046SYuri Pankov 		while (p->tcol->col < p->tcol->lastcol &&
194c66b8046SYuri Pankov 		    p->tcol->buf[p->tcol->col] == ' ')
195c66b8046SYuri Pankov 			p->tcol->col++;
19695c635efSGarrett D'Amore 
19795c635efSGarrett D'Amore 		/*
198cec8643bSMichal Nowak 		 * In multi-column mode, leave the rest of the text
199cec8643bSMichal Nowak 		 * in the buffer to be handled by a subsequent
200cec8643bSMichal Nowak 		 * invocation, such that the other columns of the
201cec8643bSMichal Nowak 		 * table can be handled first.
202cec8643bSMichal Nowak 		 * In single-column mode, simply break the line.
20395c635efSGarrett D'Amore 		 */
204c66b8046SYuri Pankov 
205c66b8046SYuri Pankov 		if (p->flags & TERMP_MULTICOL)
206c66b8046SYuri Pankov 			return;
207c66b8046SYuri Pankov 
208c66b8046SYuri Pankov 		endline(p);
209cec8643bSMichal Nowak 		p->viscol = 0;
21095c635efSGarrett D'Amore 
21195c635efSGarrett D'Amore 		/*
212cec8643bSMichal Nowak 		 * Normally, start the next line at the same indentation
213cec8643bSMichal Nowak 		 * as this one, but with the BRIND flag, start it at the
214cec8643bSMichal Nowak 		 * right margin instead.  This is used together with
215cec8643bSMichal Nowak 		 * NOBREAK for the tags in various kinds of tagged lists.
21695c635efSGarrett D'Amore 		 */
217c66b8046SYuri Pankov 
218cec8643bSMichal Nowak 		vbl = p->flags & TERMP_BRIND ?
219cec8643bSMichal Nowak 		    p->tcol->rmargin : p->tcol->offset;
220cec8643bSMichal Nowak 	}
221cec8643bSMichal Nowak 
222cec8643bSMichal Nowak 	/* Reset output state in preparation for the next field. */
22395c635efSGarrett D'Amore 
224c66b8046SYuri Pankov 	p->col = p->tcol->col = p->tcol->lastcol = 0;
225c66b8046SYuri Pankov 	p->minbl = p->trailspace;
226c66b8046SYuri Pankov 	p->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE | TERMP_NOPAD);
22795c635efSGarrett D'Amore 
228c66b8046SYuri Pankov 	if (p->flags & TERMP_MULTICOL)
22995c635efSGarrett D'Amore 		return;
23095c635efSGarrett D'Amore 
231cec8643bSMichal Nowak 	/*
232cec8643bSMichal Nowak 	 * The HANG flag means that the next field
233cec8643bSMichal Nowak 	 * always follows on the same line.
234cec8643bSMichal Nowak 	 * The NOBREAK flag means that the next field
235cec8643bSMichal Nowak 	 * follows on the same line unless the field was overrun.
236cec8643bSMichal Nowak 	 * Normally, break the line at the end of each field.
237cec8643bSMichal Nowak 	 */
238c66b8046SYuri Pankov 
239cec8643bSMichal Nowak 	if ((p->flags & TERMP_HANG) == 0 &&
240cec8643bSMichal Nowak 	    ((p->flags & TERMP_NOBREAK) == 0 ||
241cec8643bSMichal Nowak 	     vbr + term_len(p, p->trailspace) > vfield))
242c66b8046SYuri Pankov 		endline(p);
24395c635efSGarrett D'Amore }
244c66b8046SYuri Pankov 
245cec8643bSMichal Nowak /*
246cec8643bSMichal Nowak  * Store the number of input characters to print in this field in *nbr
247cec8643bSMichal Nowak  * and their total visual width to print in *vbr.
248cec8643bSMichal Nowak  * If there is only whitespace in the field, both remain zero.
249cec8643bSMichal Nowak  * The desired visual width of the field is provided by vtarget.
250cec8643bSMichal Nowak  * If the first word is longer, the field will be overrun.
251cec8643bSMichal Nowak  */
252cec8643bSMichal Nowak static void
term_fill(struct termp * p,size_t * nbr,size_t * vbr,size_t vtarget)253cec8643bSMichal Nowak term_fill(struct termp *p, size_t *nbr, size_t *vbr, size_t vtarget)
254cec8643bSMichal Nowak {
255cec8643bSMichal Nowak 	size_t	 ic;        /* Character position in the input buffer. */
256cec8643bSMichal Nowak 	size_t	 vis;       /* Visual position of the current character. */
257cec8643bSMichal Nowak 	size_t	 vn;        /* Visual position of the next character. */
258cec8643bSMichal Nowak 	int	 breakline; /* Break at the end of this word. */
259cec8643bSMichal Nowak 	int	 graph;     /* Last character was non-blank. */
260cec8643bSMichal Nowak 
261cec8643bSMichal Nowak 	*nbr = *vbr = vis = 0;
262cec8643bSMichal Nowak 	breakline = graph = 0;
263cec8643bSMichal Nowak 	for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
264cec8643bSMichal Nowak 		switch (p->tcol->buf[ic]) {
265cec8643bSMichal Nowak 		case '\b':  /* Escape \o (overstrike) or backspace markup. */
266cec8643bSMichal Nowak 			assert(ic > 0);
267cec8643bSMichal Nowak 			vis -= (*p->width)(p, p->tcol->buf[ic - 1]);
268cec8643bSMichal Nowak 			continue;
269cec8643bSMichal Nowak 
270cec8643bSMichal Nowak 		case '\t':  /* Normal ASCII whitespace. */
271cec8643bSMichal Nowak 		case ' ':
272cec8643bSMichal Nowak 		case ASCII_BREAK:  /* Escape \: (breakpoint). */
273cec8643bSMichal Nowak 			switch (p->tcol->buf[ic]) {
274cec8643bSMichal Nowak 			case '\t':
275cec8643bSMichal Nowak 				vn = term_tab_next(vis);
276cec8643bSMichal Nowak 				break;
277cec8643bSMichal Nowak 			case ' ':
278cec8643bSMichal Nowak 				vn = vis + (*p->width)(p, ' ');
279cec8643bSMichal Nowak 				break;
280cec8643bSMichal Nowak 			case ASCII_BREAK:
281cec8643bSMichal Nowak 				vn = vis;
282cec8643bSMichal Nowak 				break;
283cec8643bSMichal Nowak 			default:
284cec8643bSMichal Nowak 				abort();
285cec8643bSMichal Nowak 			}
286cec8643bSMichal Nowak 			/* Can break at the end of a word. */
287cec8643bSMichal Nowak 			if (breakline || vn > vtarget)
288cec8643bSMichal Nowak 				break;
289cec8643bSMichal Nowak 			if (graph) {
290cec8643bSMichal Nowak 				*nbr = ic;
291cec8643bSMichal Nowak 				*vbr = vis;
292cec8643bSMichal Nowak 				graph = 0;
293cec8643bSMichal Nowak 			}
294cec8643bSMichal Nowak 			vis = vn;
295cec8643bSMichal Nowak 			continue;
296cec8643bSMichal Nowak 
297cec8643bSMichal Nowak 		case '\n':  /* Escape \p (break at the end of the word). */
298cec8643bSMichal Nowak 			breakline = 1;
299cec8643bSMichal Nowak 			continue;
300cec8643bSMichal Nowak 
301cec8643bSMichal Nowak 		case ASCII_HYPH:  /* Breakable hyphen. */
302cec8643bSMichal Nowak 			graph = 1;
303cec8643bSMichal Nowak 			/*
304cec8643bSMichal Nowak 			 * We are about to decide whether to break the
305cec8643bSMichal Nowak 			 * line or not, so we no longer need this hyphen
306cec8643bSMichal Nowak 			 * to be marked as breakable.  Put back a real
307cec8643bSMichal Nowak 			 * hyphen such that we get the correct width.
308cec8643bSMichal Nowak 			 */
309cec8643bSMichal Nowak 			p->tcol->buf[ic] = '-';
310cec8643bSMichal Nowak 			vis += (*p->width)(p, '-');
311cec8643bSMichal Nowak 			if (vis > vtarget) {
312cec8643bSMichal Nowak 				ic++;
313cec8643bSMichal Nowak 				break;
314cec8643bSMichal Nowak 			}
315cec8643bSMichal Nowak 			*nbr = ic + 1;
316cec8643bSMichal Nowak 			*vbr = vis;
317cec8643bSMichal Nowak 			continue;
318cec8643bSMichal Nowak 
319cec8643bSMichal Nowak 		case ASCII_NBRSP:  /* Non-breakable space. */
320cec8643bSMichal Nowak 			p->tcol->buf[ic] = ' ';
321cec8643bSMichal Nowak 			/* FALLTHROUGH */
322cec8643bSMichal Nowak 		default:  /* Printable character. */
323cec8643bSMichal Nowak 			graph = 1;
324cec8643bSMichal Nowak 			vis += (*p->width)(p, p->tcol->buf[ic]);
325cec8643bSMichal Nowak 			if (vis > vtarget && *nbr > 0)
326cec8643bSMichal Nowak 				return;
327cec8643bSMichal Nowak 			continue;
328cec8643bSMichal Nowak 		}
329cec8643bSMichal Nowak 		break;
330cec8643bSMichal Nowak 	}
331cec8643bSMichal Nowak 
332cec8643bSMichal Nowak 	/*
333cec8643bSMichal Nowak 	 * If the last word extends to the end of the field without any
334cec8643bSMichal Nowak 	 * trailing whitespace, the loop could not check yet whether it
335cec8643bSMichal Nowak 	 * can remain on this line.  So do the check now.
336cec8643bSMichal Nowak 	 */
337cec8643bSMichal Nowak 
338cec8643bSMichal Nowak 	if (graph && (vis <= vtarget || *nbr == 0)) {
339cec8643bSMichal Nowak 		*nbr = ic;
340cec8643bSMichal Nowak 		*vbr = vis;
341cec8643bSMichal Nowak 	}
342cec8643bSMichal Nowak }
343cec8643bSMichal Nowak 
344cec8643bSMichal Nowak /*
345cec8643bSMichal Nowak  * Print the contents of one field
346cec8643bSMichal Nowak  * with an indentation of	 vbl	  visual columns,
347*4d131170SRobert Mustacchi  * and an input string length of nbr	  characters.
348cec8643bSMichal Nowak  */
349cec8643bSMichal Nowak static void
term_field(struct termp * p,size_t vbl,size_t nbr)350*4d131170SRobert Mustacchi term_field(struct termp *p, size_t vbl, size_t nbr)
351cec8643bSMichal Nowak {
352cec8643bSMichal Nowak 	size_t	 ic;	/* Character position in the input buffer. */
353cec8643bSMichal Nowak 	size_t	 vis;	/* Visual position of the current character. */
354cec8643bSMichal Nowak 	size_t	 dv;	/* Visual width of the current character. */
355cec8643bSMichal Nowak 	size_t	 vn;	/* Visual position of the next character. */
356cec8643bSMichal Nowak 
357cec8643bSMichal Nowak 	vis = 0;
358cec8643bSMichal Nowak 	for (ic = p->tcol->col; ic < nbr; ic++) {
359cec8643bSMichal Nowak 
360cec8643bSMichal Nowak 		/*
361cec8643bSMichal Nowak 		 * To avoid the printing of trailing whitespace,
362cec8643bSMichal Nowak 		 * do not print whitespace right away, only count it.
363cec8643bSMichal Nowak 		 */
364cec8643bSMichal Nowak 
365cec8643bSMichal Nowak 		switch (p->tcol->buf[ic]) {
366cec8643bSMichal Nowak 		case '\n':
367cec8643bSMichal Nowak 		case ASCII_BREAK:
368cec8643bSMichal Nowak 			continue;
369cec8643bSMichal Nowak 		case '\t':
370cec8643bSMichal Nowak 			vn = term_tab_next(vis);
371cec8643bSMichal Nowak 			vbl += vn - vis;
372cec8643bSMichal Nowak 			vis = vn;
373cec8643bSMichal Nowak 			continue;
374cec8643bSMichal Nowak 		case ' ':
375cec8643bSMichal Nowak 		case ASCII_NBRSP:
376cec8643bSMichal Nowak 			dv = (*p->width)(p, ' ');
377cec8643bSMichal Nowak 			vbl += dv;
378cec8643bSMichal Nowak 			vis += dv;
379cec8643bSMichal Nowak 			continue;
380cec8643bSMichal Nowak 		default:
381cec8643bSMichal Nowak 			break;
382cec8643bSMichal Nowak 		}
383cec8643bSMichal Nowak 
384cec8643bSMichal Nowak 		/*
385cec8643bSMichal Nowak 		 * We found a non-blank character to print,
386cec8643bSMichal Nowak 		 * so write preceding white space now.
387cec8643bSMichal Nowak 		 */
388cec8643bSMichal Nowak 
389cec8643bSMichal Nowak 		if (vbl > 0) {
390cec8643bSMichal Nowak 			(*p->advance)(p, vbl);
391cec8643bSMichal Nowak 			p->viscol += vbl;
392cec8643bSMichal Nowak 			vbl = 0;
393cec8643bSMichal Nowak 		}
394cec8643bSMichal Nowak 
395cec8643bSMichal Nowak 		/* Print the character and adjust the visual position. */
396cec8643bSMichal Nowak 
397cec8643bSMichal Nowak 		(*p->letter)(p, p->tcol->buf[ic]);
398cec8643bSMichal Nowak 		if (p->tcol->buf[ic] == '\b') {
399cec8643bSMichal Nowak 			dv = (*p->width)(p, p->tcol->buf[ic - 1]);
400cec8643bSMichal Nowak 			p->viscol -= dv;
401cec8643bSMichal Nowak 			vis -= dv;
402cec8643bSMichal Nowak 		} else {
403cec8643bSMichal Nowak 			dv = (*p->width)(p, p->tcol->buf[ic]);
404cec8643bSMichal Nowak 			p->viscol += dv;
405cec8643bSMichal Nowak 			vis += dv;
406cec8643bSMichal Nowak 		}
407cec8643bSMichal Nowak 	}
408cec8643bSMichal Nowak 	p->tcol->col = nbr;
409cec8643bSMichal Nowak }
410cec8643bSMichal Nowak 
411c66b8046SYuri Pankov static void
endline(struct termp * p)412c66b8046SYuri Pankov endline(struct termp *p)
413c66b8046SYuri Pankov {
414c66b8046SYuri Pankov 	if ((p->flags & (TERMP_NEWMC | TERMP_ENDMC)) == TERMP_ENDMC) {
415c66b8046SYuri Pankov 		p->mc = NULL;
416c66b8046SYuri Pankov 		p->flags &= ~TERMP_ENDMC;
417c66b8046SYuri Pankov 	}
418c66b8046SYuri Pankov 	if (p->mc != NULL) {
419c66b8046SYuri Pankov 		if (p->viscol && p->maxrmargin >= p->viscol)
420c66b8046SYuri Pankov 			(*p->advance)(p, p->maxrmargin - p->viscol + 1);
421c66b8046SYuri Pankov 		p->flags |= TERMP_NOBUF | TERMP_NOSPACE;
422c66b8046SYuri Pankov 		term_word(p, p->mc);
423c66b8046SYuri Pankov 		p->flags &= ~(TERMP_NOBUF | TERMP_NEWMC);
424c66b8046SYuri Pankov 	}
425c66b8046SYuri Pankov 	p->viscol = 0;
426c66b8046SYuri Pankov 	p->minbl = 0;
427c66b8046SYuri Pankov 	(*p->endline)(p);
42895c635efSGarrett D'Amore }
42995c635efSGarrett D'Amore 
43095c635efSGarrett D'Amore /*
43195c635efSGarrett D'Amore  * A newline only breaks an existing line; it won't assert vertical
43295c635efSGarrett D'Amore  * space.  All data in the output buffer is flushed prior to the newline
43395c635efSGarrett D'Amore  * assertion.
43495c635efSGarrett D'Amore  */
43595c635efSGarrett D'Amore void
term_newln(struct termp * p)43695c635efSGarrett D'Amore term_newln(struct termp *p)
43795c635efSGarrett D'Amore {
43895c635efSGarrett D'Amore 
43995c635efSGarrett D'Amore 	p->flags |= TERMP_NOSPACE;
440c66b8046SYuri Pankov 	if (p->tcol->lastcol || p->viscol)
44195c635efSGarrett D'Amore 		term_flushln(p);
44295c635efSGarrett D'Amore }
44395c635efSGarrett D'Amore 
44495c635efSGarrett D'Amore /*
44595c635efSGarrett D'Amore  * Asserts a vertical space (a full, empty line-break between lines).
44695c635efSGarrett D'Amore  * Note that if used twice, this will cause two blank spaces and so on.
44795c635efSGarrett D'Amore  * All data in the output buffer is flushed prior to the newline
44895c635efSGarrett D'Amore  * assertion.
44995c635efSGarrett D'Amore  */
45095c635efSGarrett D'Amore void
term_vspace(struct termp * p)45195c635efSGarrett D'Amore term_vspace(struct termp *p)
45295c635efSGarrett D'Amore {
45395c635efSGarrett D'Amore 
45495c635efSGarrett D'Amore 	term_newln(p);
45595c635efSGarrett D'Amore 	p->viscol = 0;
456c66b8046SYuri Pankov 	p->minbl = 0;
457698f87a4SGarrett D'Amore 	if (0 < p->skipvsp)
458698f87a4SGarrett D'Amore 		p->skipvsp--;
459698f87a4SGarrett D'Amore 	else
46095c635efSGarrett D'Amore 		(*p->endline)(p);
46195c635efSGarrett D'Amore }
46295c635efSGarrett D'Amore 
463260e9a87SYuri Pankov /* Swap current and previous font; for \fP and .ft P */
46495c635efSGarrett D'Amore void
term_fontlast(struct termp * p)46595c635efSGarrett D'Amore term_fontlast(struct termp *p)
46695c635efSGarrett D'Amore {
46795c635efSGarrett D'Amore 	enum termfont	 f;
46895c635efSGarrett D'Amore 
46995c635efSGarrett D'Amore 	f = p->fontl;
47095c635efSGarrett D'Amore 	p->fontl = p->fontq[p->fonti];
47195c635efSGarrett D'Amore 	p->fontq[p->fonti] = f;
47295c635efSGarrett D'Amore }
47395c635efSGarrett D'Amore 
474260e9a87SYuri Pankov /* Set font, save current, discard previous; for \f, .ft, .B etc. */
47595c635efSGarrett D'Amore void
term_fontrepl(struct termp * p,enum termfont f)47695c635efSGarrett D'Amore term_fontrepl(struct termp *p, enum termfont f)
47795c635efSGarrett D'Amore {
47895c635efSGarrett D'Amore 
47995c635efSGarrett D'Amore 	p->fontl = p->fontq[p->fonti];
48095c635efSGarrett D'Amore 	p->fontq[p->fonti] = f;
48195c635efSGarrett D'Amore }
48295c635efSGarrett D'Amore 
483260e9a87SYuri Pankov /* Set font, save previous. */
48495c635efSGarrett D'Amore void
term_fontpush(struct termp * p,enum termfont f)48595c635efSGarrett D'Amore term_fontpush(struct termp *p, enum termfont f)
48695c635efSGarrett D'Amore {
48795c635efSGarrett D'Amore 
48895c635efSGarrett D'Amore 	p->fontl = p->fontq[p->fonti];
489260e9a87SYuri Pankov 	if (++p->fonti == p->fontsz) {
490260e9a87SYuri Pankov 		p->fontsz += 8;
491260e9a87SYuri Pankov 		p->fontq = mandoc_reallocarray(p->fontq,
492371584c2SYuri Pankov 		    p->fontsz, sizeof(*p->fontq));
493260e9a87SYuri Pankov 	}
494260e9a87SYuri Pankov 	p->fontq[p->fonti] = f;
49595c635efSGarrett D'Amore }
49695c635efSGarrett D'Amore 
497260e9a87SYuri Pankov /* Flush to make the saved pointer current again. */
49895c635efSGarrett D'Amore void
term_fontpopq(struct termp * p,int i)499260e9a87SYuri Pankov term_fontpopq(struct termp *p, int i)
50095c635efSGarrett D'Amore {
50195c635efSGarrett D'Amore 
502260e9a87SYuri Pankov 	assert(i >= 0);
503260e9a87SYuri Pankov 	if (p->fonti > i)
504260e9a87SYuri Pankov 		p->fonti = i;
50595c635efSGarrett D'Amore }
50695c635efSGarrett D'Amore 
507260e9a87SYuri Pankov /* Pop one font off the stack. */
50895c635efSGarrett D'Amore void
term_fontpop(struct termp * p)50995c635efSGarrett D'Amore term_fontpop(struct termp *p)
51095c635efSGarrett D'Amore {
51195c635efSGarrett D'Amore 
51295c635efSGarrett D'Amore 	assert(p->fonti);
51395c635efSGarrett D'Amore 	p->fonti--;
51495c635efSGarrett D'Amore }
51595c635efSGarrett D'Amore 
51695c635efSGarrett D'Amore /*
51795c635efSGarrett D'Amore  * Handle pwords, partial words, which may be either a single word or a
51895c635efSGarrett D'Amore  * phrase that cannot be broken down (such as a literal string).  This
51995c635efSGarrett D'Amore  * handles word styling.
52095c635efSGarrett D'Amore  */
52195c635efSGarrett D'Amore void
term_word(struct termp * p,const char * word)52295c635efSGarrett D'Amore term_word(struct termp *p, const char *word)
52395c635efSGarrett D'Amore {
524c66b8046SYuri Pankov 	struct roffsu	 su;
525698f87a4SGarrett D'Amore 	const char	 nbrsp[2] = { ASCII_NBRSP, 0 };
52695c635efSGarrett D'Amore 	const char	*seq, *cp;
52795c635efSGarrett D'Amore 	int		 sz, uc;
528c66b8046SYuri Pankov 	size_t		 csz, lsz, ssz;
52995c635efSGarrett D'Amore 	enum mandoc_esc	 esc;
53095c635efSGarrett D'Amore 
531c66b8046SYuri Pankov 	if ((p->flags & TERMP_NOBUF) == 0) {
532c66b8046SYuri Pankov 		if ((p->flags & TERMP_NOSPACE) == 0) {
533c66b8046SYuri Pankov 			if ((p->flags & TERMP_KEEP) == 0) {
53495c635efSGarrett D'Amore 				bufferc(p, ' ');
535c66b8046SYuri Pankov 				if (p->flags & TERMP_SENTENCE)
53695c635efSGarrett D'Amore 					bufferc(p, ' ');
53795c635efSGarrett D'Amore 			} else
53895c635efSGarrett D'Amore 				bufferc(p, ASCII_NBRSP);
53995c635efSGarrett D'Amore 		}
540c66b8046SYuri Pankov 		if (p->flags & TERMP_PREKEEP)
541698f87a4SGarrett D'Amore 			p->flags |= TERMP_KEEP;
542c66b8046SYuri Pankov 		if (p->flags & TERMP_NONOSPACE)
54395c635efSGarrett D'Amore 			p->flags |= TERMP_NOSPACE;
544c66b8046SYuri Pankov 		else
545c66b8046SYuri Pankov 			p->flags &= ~TERMP_NOSPACE;
546260e9a87SYuri Pankov 		p->flags &= ~(TERMP_SENTENCE | TERMP_NONEWLINE);
547260e9a87SYuri Pankov 		p->skipvsp = 0;
548c66b8046SYuri Pankov 	}
54995c635efSGarrett D'Amore 
55095c635efSGarrett D'Amore 	while ('\0' != *word) {
551698f87a4SGarrett D'Amore 		if ('\\' != *word) {
552698f87a4SGarrett D'Amore 			if (TERMP_NBRWORD & p->flags) {
553698f87a4SGarrett D'Amore 				if (' ' == *word) {
554698f87a4SGarrett D'Amore 					encode(p, nbrsp, 1);
555698f87a4SGarrett D'Amore 					word++;
556698f87a4SGarrett D'Amore 					continue;
557698f87a4SGarrett D'Amore 				}
558698f87a4SGarrett D'Amore 				ssz = strcspn(word, "\\ ");
559698f87a4SGarrett D'Amore 			} else
560698f87a4SGarrett D'Amore 				ssz = strcspn(word, "\\");
561698f87a4SGarrett D'Amore 			encode(p, word, ssz);
562698f87a4SGarrett D'Amore 			word += (int)ssz;
563698f87a4SGarrett D'Amore 			continue;
564698f87a4SGarrett D'Amore 		}
56595c635efSGarrett D'Amore 
56695c635efSGarrett D'Amore 		word++;
56795c635efSGarrett D'Amore 		esc = mandoc_escape(&word, &seq, &sz);
56895c635efSGarrett D'Amore 		switch (esc) {
569260e9a87SYuri Pankov 		case ESCAPE_UNICODE:
57095c635efSGarrett D'Amore 			uc = mchars_num2uc(seq + 1, sz - 1);
57195c635efSGarrett D'Amore 			break;
572260e9a87SYuri Pankov 		case ESCAPE_NUMBERED:
573260e9a87SYuri Pankov 			uc = mchars_num2char(seq, sz);
574260e9a87SYuri Pankov 			if (uc < 0)
57595c635efSGarrett D'Amore 				continue;
57695c635efSGarrett D'Amore 			break;
577260e9a87SYuri Pankov 		case ESCAPE_SPECIAL:
578260e9a87SYuri Pankov 			if (p->enc == TERMENC_ASCII) {
579371584c2SYuri Pankov 				cp = mchars_spec2str(seq, sz, &ssz);
580260e9a87SYuri Pankov 				if (cp != NULL)
58195c635efSGarrett D'Amore 					encode(p, cp, ssz);
582260e9a87SYuri Pankov 			} else {
583371584c2SYuri Pankov 				uc = mchars_spec2cp(seq, sz);
584260e9a87SYuri Pankov 				if (uc > 0)
585260e9a87SYuri Pankov 					encode1(p, uc);
586260e9a87SYuri Pankov 			}
587260e9a87SYuri Pankov 			continue;
588cec8643bSMichal Nowak 		case ESCAPE_UNDEF:
589cec8643bSMichal Nowak 			uc = *seq;
590cec8643bSMichal Nowak 			break;
591260e9a87SYuri Pankov 		case ESCAPE_FONTBOLD:
592*4d131170SRobert Mustacchi 		case ESCAPE_FONTCB:
59395c635efSGarrett D'Amore 			term_fontrepl(p, TERMFONT_BOLD);
594260e9a87SYuri Pankov 			continue;
595260e9a87SYuri Pankov 		case ESCAPE_FONTITALIC:
596*4d131170SRobert Mustacchi 		case ESCAPE_FONTCI:
59795c635efSGarrett D'Amore 			term_fontrepl(p, TERMFONT_UNDER);
598260e9a87SYuri Pankov 			continue;
599260e9a87SYuri Pankov 		case ESCAPE_FONTBI:
600698f87a4SGarrett D'Amore 			term_fontrepl(p, TERMFONT_BI);
601260e9a87SYuri Pankov 			continue;
602260e9a87SYuri Pankov 		case ESCAPE_FONT:
603*4d131170SRobert Mustacchi 		case ESCAPE_FONTCR:
604260e9a87SYuri Pankov 		case ESCAPE_FONTROMAN:
60595c635efSGarrett D'Amore 			term_fontrepl(p, TERMFONT_NONE);
606260e9a87SYuri Pankov 			continue;
607260e9a87SYuri Pankov 		case ESCAPE_FONTPREV:
60895c635efSGarrett D'Amore 			term_fontlast(p);
609260e9a87SYuri Pankov 			continue;
610c66b8046SYuri Pankov 		case ESCAPE_BREAK:
611c66b8046SYuri Pankov 			bufferc(p, '\n');
612c66b8046SYuri Pankov 			continue;
613260e9a87SYuri Pankov 		case ESCAPE_NOSPACE:
614371584c2SYuri Pankov 			if (p->flags & TERMP_BACKAFTER)
615371584c2SYuri Pankov 				p->flags &= ~TERMP_BACKAFTER;
616371584c2SYuri Pankov 			else if (*word == '\0')
617260e9a87SYuri Pankov 				p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE);
618260e9a87SYuri Pankov 			continue;
619cec8643bSMichal Nowak 		case ESCAPE_DEVICE:
620cec8643bSMichal Nowak 			if (p->type == TERMTYPE_PDF)
621cec8643bSMichal Nowak 				encode(p, "pdf", 3);
622cec8643bSMichal Nowak 			else if (p->type == TERMTYPE_PS)
623cec8643bSMichal Nowak 				encode(p, "ps", 2);
624cec8643bSMichal Nowak 			else if (p->enc == TERMENC_ASCII)
625cec8643bSMichal Nowak 				encode(p, "ascii", 5);
626cec8643bSMichal Nowak 			else
627cec8643bSMichal Nowak 				encode(p, "utf8", 4);
628cec8643bSMichal Nowak 			continue;
629c66b8046SYuri Pankov 		case ESCAPE_HORIZ:
630c66b8046SYuri Pankov 			if (*seq == '|') {
631c66b8046SYuri Pankov 				seq++;
632c66b8046SYuri Pankov 				uc = -p->col;
633c66b8046SYuri Pankov 			} else
634c66b8046SYuri Pankov 				uc = 0;
635c66b8046SYuri Pankov 			if (a2roffsu(seq, &su, SCALE_EM) == NULL)
636c66b8046SYuri Pankov 				continue;
637c66b8046SYuri Pankov 			uc += term_hen(p, &su);
638c66b8046SYuri Pankov 			if (uc > 0)
639c66b8046SYuri Pankov 				while (uc-- > 0)
640c66b8046SYuri Pankov 					bufferc(p, ASCII_NBRSP);
641c66b8046SYuri Pankov 			else if (p->col > (size_t)(-uc))
642c66b8046SYuri Pankov 				p->col += uc;
643c66b8046SYuri Pankov 			else {
644c66b8046SYuri Pankov 				uc += p->col;
645c66b8046SYuri Pankov 				p->col = 0;
646c66b8046SYuri Pankov 				if (p->tcol->offset > (size_t)(-uc)) {
647c66b8046SYuri Pankov 					p->ti += uc;
648c66b8046SYuri Pankov 					p->tcol->offset += uc;
649c66b8046SYuri Pankov 				} else {
650c66b8046SYuri Pankov 					p->ti -= p->tcol->offset;
651c66b8046SYuri Pankov 					p->tcol->offset = 0;
652c66b8046SYuri Pankov 				}
653c66b8046SYuri Pankov 			}
654c66b8046SYuri Pankov 			continue;
655c66b8046SYuri Pankov 		case ESCAPE_HLINE:
656c66b8046SYuri Pankov 			if ((cp = a2roffsu(seq, &su, SCALE_EM)) == NULL)
657c66b8046SYuri Pankov 				continue;
658c66b8046SYuri Pankov 			uc = term_hen(p, &su);
659c66b8046SYuri Pankov 			if (uc <= 0) {
660c66b8046SYuri Pankov 				if (p->tcol->rmargin <= p->tcol->offset)
661c66b8046SYuri Pankov 					continue;
662c66b8046SYuri Pankov 				lsz = p->tcol->rmargin - p->tcol->offset;
663c66b8046SYuri Pankov 			} else
664c66b8046SYuri Pankov 				lsz = uc;
665c66b8046SYuri Pankov 			if (*cp == seq[-1])
666c66b8046SYuri Pankov 				uc = -1;
667c66b8046SYuri Pankov 			else if (*cp == '\\') {
668c66b8046SYuri Pankov 				seq = cp + 1;
669c66b8046SYuri Pankov 				esc = mandoc_escape(&seq, &cp, &sz);
670c66b8046SYuri Pankov 				switch (esc) {
671c66b8046SYuri Pankov 				case ESCAPE_UNICODE:
672c66b8046SYuri Pankov 					uc = mchars_num2uc(cp + 1, sz - 1);
673c66b8046SYuri Pankov 					break;
674c66b8046SYuri Pankov 				case ESCAPE_NUMBERED:
675c66b8046SYuri Pankov 					uc = mchars_num2char(cp, sz);
676c66b8046SYuri Pankov 					break;
677c66b8046SYuri Pankov 				case ESCAPE_SPECIAL:
678c66b8046SYuri Pankov 					uc = mchars_spec2cp(cp, sz);
679c66b8046SYuri Pankov 					break;
680cec8643bSMichal Nowak 				case ESCAPE_UNDEF:
681cec8643bSMichal Nowak 					uc = *seq;
682cec8643bSMichal Nowak 					break;
683c66b8046SYuri Pankov 				default:
684c66b8046SYuri Pankov 					uc = -1;
685c66b8046SYuri Pankov 					break;
686c66b8046SYuri Pankov 				}
687c66b8046SYuri Pankov 			} else
688c66b8046SYuri Pankov 				uc = *cp;
689c66b8046SYuri Pankov 			if (uc < 0x20 || (uc > 0x7E && uc < 0xA0))
690c66b8046SYuri Pankov 				uc = '_';
691c66b8046SYuri Pankov 			if (p->enc == TERMENC_ASCII) {
692c66b8046SYuri Pankov 				cp = ascii_uc2str(uc);
693c66b8046SYuri Pankov 				csz = term_strlen(p, cp);
694c66b8046SYuri Pankov 				ssz = strlen(cp);
695c66b8046SYuri Pankov 			} else
696c66b8046SYuri Pankov 				csz = (*p->width)(p, uc);
697c66b8046SYuri Pankov 			while (lsz >= csz) {
698c66b8046SYuri Pankov 				if (p->enc == TERMENC_ASCII)
699c66b8046SYuri Pankov 					encode(p, cp, ssz);
700c66b8046SYuri Pankov 				else
701c66b8046SYuri Pankov 					encode1(p, uc);
702c66b8046SYuri Pankov 				lsz -= csz;
703c66b8046SYuri Pankov 			}
704c66b8046SYuri Pankov 			continue;
705260e9a87SYuri Pankov 		case ESCAPE_SKIPCHAR:
706371584c2SYuri Pankov 			p->flags |= TERMP_BACKAFTER;
707260e9a87SYuri Pankov 			continue;
708260e9a87SYuri Pankov 		case ESCAPE_OVERSTRIKE:
709260e9a87SYuri Pankov 			cp = seq + sz;
710260e9a87SYuri Pankov 			while (seq < cp) {
711260e9a87SYuri Pankov 				if (*seq == '\\') {
712260e9a87SYuri Pankov 					mandoc_escape(&seq, NULL, NULL);
713260e9a87SYuri Pankov 					continue;
714260e9a87SYuri Pankov 				}
715260e9a87SYuri Pankov 				encode1(p, *seq++);
716371584c2SYuri Pankov 				if (seq < cp) {
717371584c2SYuri Pankov 					if (p->flags & TERMP_BACKBEFORE)
718371584c2SYuri Pankov 						p->flags |= TERMP_BACKAFTER;
719371584c2SYuri Pankov 					else
720371584c2SYuri Pankov 						p->flags |= TERMP_BACKBEFORE;
721260e9a87SYuri Pankov 				}
722371584c2SYuri Pankov 			}
723371584c2SYuri Pankov 			/* Trim trailing backspace/blank pair. */
724c66b8046SYuri Pankov 			if (p->tcol->lastcol > 2 &&
725c66b8046SYuri Pankov 			    (p->tcol->buf[p->tcol->lastcol - 1] == ' ' ||
726c66b8046SYuri Pankov 			     p->tcol->buf[p->tcol->lastcol - 1] == '\t'))
727c66b8046SYuri Pankov 				p->tcol->lastcol -= 2;
728c66b8046SYuri Pankov 			if (p->col > p->tcol->lastcol)
729c66b8046SYuri Pankov 				p->col = p->tcol->lastcol;
730371584c2SYuri Pankov 			continue;
73195c635efSGarrett D'Amore 		default:
732260e9a87SYuri Pankov 			continue;
733260e9a87SYuri Pankov 		}
734260e9a87SYuri Pankov 
735260e9a87SYuri Pankov 		/*
736260e9a87SYuri Pankov 		 * Common handling for Unicode and numbered
737260e9a87SYuri Pankov 		 * character escape sequences.
738260e9a87SYuri Pankov 		 */
739260e9a87SYuri Pankov 
740260e9a87SYuri Pankov 		if (p->enc == TERMENC_ASCII) {
741260e9a87SYuri Pankov 			cp = ascii_uc2str(uc);
742260e9a87SYuri Pankov 			encode(p, cp, strlen(cp));
743260e9a87SYuri Pankov 		} else {
744260e9a87SYuri Pankov 			if ((uc < 0x20 && uc != 0x09) ||
745260e9a87SYuri Pankov 			    (uc > 0x7E && uc < 0xA0))
746260e9a87SYuri Pankov 				uc = 0xFFFD;
747260e9a87SYuri Pankov 			encode1(p, uc);
74895c635efSGarrett D'Amore 		}
74995c635efSGarrett D'Amore 	}
750698f87a4SGarrett D'Amore 	p->flags &= ~TERMP_NBRWORD;
75195c635efSGarrett D'Amore }
75295c635efSGarrett D'Amore 
75395c635efSGarrett D'Amore static void
adjbuf(struct termp_col * c,size_t sz)754c66b8046SYuri Pankov adjbuf(struct termp_col *c, size_t sz)
75595c635efSGarrett D'Amore {
756c66b8046SYuri Pankov 	if (c->maxcols == 0)
757c66b8046SYuri Pankov 		c->maxcols = 1024;
758c66b8046SYuri Pankov 	while (c->maxcols <= sz)
759c66b8046SYuri Pankov 		c->maxcols <<= 2;
760c66b8046SYuri Pankov 	c->buf = mandoc_reallocarray(c->buf, c->maxcols, sizeof(*c->buf));
76195c635efSGarrett D'Amore }
76295c635efSGarrett D'Amore 
76395c635efSGarrett D'Amore static void
bufferc(struct termp * p,char c)76495c635efSGarrett D'Amore bufferc(struct termp *p, char c)
76595c635efSGarrett D'Amore {
766c66b8046SYuri Pankov 	if (p->flags & TERMP_NOBUF) {
767c66b8046SYuri Pankov 		(*p->letter)(p, c);
768c66b8046SYuri Pankov 		return;
769c66b8046SYuri Pankov 	}
770c66b8046SYuri Pankov 	if (p->col + 1 >= p->tcol->maxcols)
771c66b8046SYuri Pankov 		adjbuf(p->tcol, p->col + 1);
772c66b8046SYuri Pankov 	if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
773c66b8046SYuri Pankov 		p->tcol->buf[p->col] = c;
774c66b8046SYuri Pankov 	if (p->tcol->lastcol < ++p->col)
775c66b8046SYuri Pankov 		p->tcol->lastcol = p->col;
77695c635efSGarrett D'Amore }
77795c635efSGarrett D'Amore 
77895c635efSGarrett D'Amore /*
77995c635efSGarrett D'Amore  * See encode().
78095c635efSGarrett D'Amore  * Do this for a single (probably unicode) value.
78195c635efSGarrett D'Amore  * Does not check for non-decorated glyphs.
78295c635efSGarrett D'Amore  */
78395c635efSGarrett D'Amore static void
encode1(struct termp * p,int c)78495c635efSGarrett D'Amore encode1(struct termp *p, int c)
78595c635efSGarrett D'Amore {
78695c635efSGarrett D'Amore 	enum termfont	  f;
78795c635efSGarrett D'Amore 
788c66b8046SYuri Pankov 	if (p->flags & TERMP_NOBUF) {
789c66b8046SYuri Pankov 		(*p->letter)(p, c);
790c66b8046SYuri Pankov 		return;
791c66b8046SYuri Pankov 	}
792c66b8046SYuri Pankov 
793c66b8046SYuri Pankov 	if (p->col + 7 >= p->tcol->maxcols)
794c66b8046SYuri Pankov 		adjbuf(p->tcol, p->col + 7);
795371584c2SYuri Pankov 
796371584c2SYuri Pankov 	f = (c == ASCII_HYPH || c > 127 || isgraph(c)) ?
797371584c2SYuri Pankov 	    p->fontq[p->fonti] : TERMFONT_NONE;
798371584c2SYuri Pankov 
799371584c2SYuri Pankov 	if (p->flags & TERMP_BACKBEFORE) {
800c66b8046SYuri Pankov 		if (p->tcol->buf[p->col - 1] == ' ' ||
801c66b8046SYuri Pankov 		    p->tcol->buf[p->col - 1] == '\t')
802371584c2SYuri Pankov 			p->col--;
803371584c2SYuri Pankov 		else
804c66b8046SYuri Pankov 			p->tcol->buf[p->col++] = '\b';
805371584c2SYuri Pankov 		p->flags &= ~TERMP_BACKBEFORE;
806698f87a4SGarrett D'Amore 	}
807c66b8046SYuri Pankov 	if (f == TERMFONT_UNDER || f == TERMFONT_BI) {
808c66b8046SYuri Pankov 		p->tcol->buf[p->col++] = '_';
809c66b8046SYuri Pankov 		p->tcol->buf[p->col++] = '\b';
810698f87a4SGarrett D'Amore 	}
811c66b8046SYuri Pankov 	if (f == TERMFONT_BOLD || f == TERMFONT_BI) {
812c66b8046SYuri Pankov 		if (c == ASCII_HYPH)
813c66b8046SYuri Pankov 			p->tcol->buf[p->col++] = '-';
814698f87a4SGarrett D'Amore 		else
815c66b8046SYuri Pankov 			p->tcol->buf[p->col++] = c;
816c66b8046SYuri Pankov 		p->tcol->buf[p->col++] = '\b';
817698f87a4SGarrett D'Amore 	}
818c66b8046SYuri Pankov 	if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
819c66b8046SYuri Pankov 		p->tcol->buf[p->col] = c;
820c66b8046SYuri Pankov 	if (p->tcol->lastcol < ++p->col)
821c66b8046SYuri Pankov 		p->tcol->lastcol = p->col;
822371584c2SYuri Pankov 	if (p->flags & TERMP_BACKAFTER) {
823371584c2SYuri Pankov 		p->flags |= TERMP_BACKBEFORE;
824371584c2SYuri Pankov 		p->flags &= ~TERMP_BACKAFTER;
825371584c2SYuri Pankov 	}
82695c635efSGarrett D'Amore }
82795c635efSGarrett D'Amore 
82895c635efSGarrett D'Amore static void
encode(struct termp * p,const char * word,size_t sz)82995c635efSGarrett D'Amore encode(struct termp *p, const char *word, size_t sz)
83095c635efSGarrett D'Amore {
831698f87a4SGarrett D'Amore 	size_t		  i;
83295c635efSGarrett D'Amore 
833c66b8046SYuri Pankov 	if (p->flags & TERMP_NOBUF) {
834c66b8046SYuri Pankov 		for (i = 0; i < sz; i++)
835c66b8046SYuri Pankov 			(*p->letter)(p, word[i]);
836c66b8046SYuri Pankov 		return;
837c66b8046SYuri Pankov 	}
838c66b8046SYuri Pankov 
839c66b8046SYuri Pankov 	if (p->col + 2 + (sz * 5) >= p->tcol->maxcols)
840c66b8046SYuri Pankov 		adjbuf(p->tcol, p->col + 2 + (sz * 5));
84195c635efSGarrett D'Amore 
842698f87a4SGarrett D'Amore 	for (i = 0; i < sz; i++) {
843698f87a4SGarrett D'Amore 		if (ASCII_HYPH == word[i] ||
844698f87a4SGarrett D'Amore 		    isgraph((unsigned char)word[i]))
845698f87a4SGarrett D'Amore 			encode1(p, word[i]);
846a40ea1a7SYuri Pankov 		else {
847c66b8046SYuri Pankov 			if (p->tcol->lastcol <= p->col ||
848c66b8046SYuri Pankov 			    (word[i] != ' ' && word[i] != ASCII_NBRSP))
849c66b8046SYuri Pankov 				p->tcol->buf[p->col] = word[i];
850c66b8046SYuri Pankov 			p->col++;
851a40ea1a7SYuri Pankov 
852a40ea1a7SYuri Pankov 			/*
853a40ea1a7SYuri Pankov 			 * Postpone the effect of \z while handling
854a40ea1a7SYuri Pankov 			 * an overstrike sequence from ascii_uc2str().
855a40ea1a7SYuri Pankov 			 */
856a40ea1a7SYuri Pankov 
857a40ea1a7SYuri Pankov 			if (word[i] == '\b' &&
858a40ea1a7SYuri Pankov 			    (p->flags & TERMP_BACKBEFORE)) {
859a40ea1a7SYuri Pankov 				p->flags &= ~TERMP_BACKBEFORE;
860a40ea1a7SYuri Pankov 				p->flags |= TERMP_BACKAFTER;
861a40ea1a7SYuri Pankov 			}
862a40ea1a7SYuri Pankov 		}
86395c635efSGarrett D'Amore 	}
864c66b8046SYuri Pankov 	if (p->tcol->lastcol < p->col)
865c66b8046SYuri Pankov 		p->tcol->lastcol = p->col;
86695c635efSGarrett D'Amore }
86795c635efSGarrett D'Amore 
868260e9a87SYuri Pankov void
term_setwidth(struct termp * p,const char * wstr)869260e9a87SYuri Pankov term_setwidth(struct termp *p, const char *wstr)
870260e9a87SYuri Pankov {
871260e9a87SYuri Pankov 	struct roffsu	 su;
872371584c2SYuri Pankov 	int		 iop, width;
873260e9a87SYuri Pankov 
874260e9a87SYuri Pankov 	iop = 0;
875260e9a87SYuri Pankov 	width = 0;
876260e9a87SYuri Pankov 	if (NULL != wstr) {
877260e9a87SYuri Pankov 		switch (*wstr) {
878260e9a87SYuri Pankov 		case '+':
879260e9a87SYuri Pankov 			iop = 1;
880260e9a87SYuri Pankov 			wstr++;
881260e9a87SYuri Pankov 			break;
882260e9a87SYuri Pankov 		case '-':
883260e9a87SYuri Pankov 			iop = -1;
884260e9a87SYuri Pankov 			wstr++;
885260e9a87SYuri Pankov 			break;
886260e9a87SYuri Pankov 		default:
887260e9a87SYuri Pankov 			break;
888260e9a87SYuri Pankov 		}
889c66b8046SYuri Pankov 		if (a2roffsu(wstr, &su, SCALE_MAX) != NULL)
890260e9a87SYuri Pankov 			width = term_hspan(p, &su);
891260e9a87SYuri Pankov 		else
892260e9a87SYuri Pankov 			iop = 0;
893260e9a87SYuri Pankov 	}
894260e9a87SYuri Pankov 	(*p->setwidth)(p, iop, width);
895260e9a87SYuri Pankov }
896260e9a87SYuri Pankov 
89795c635efSGarrett D'Amore size_t
term_len(const struct termp * p,size_t sz)89895c635efSGarrett D'Amore term_len(const struct termp *p, size_t sz)
89995c635efSGarrett D'Amore {
90095c635efSGarrett D'Amore 
901371584c2SYuri Pankov 	return (*p->width)(p, ' ') * sz;
90295c635efSGarrett D'Amore }
90395c635efSGarrett D'Amore 
904698f87a4SGarrett D'Amore static size_t
cond_width(const struct termp * p,int c,int * skip)905698f87a4SGarrett D'Amore cond_width(const struct termp *p, int c, int *skip)
906698f87a4SGarrett D'Amore {
907698f87a4SGarrett D'Amore 
908698f87a4SGarrett D'Amore 	if (*skip) {
909698f87a4SGarrett D'Amore 		(*skip) = 0;
910371584c2SYuri Pankov 		return 0;
911698f87a4SGarrett D'Amore 	} else
912371584c2SYuri Pankov 		return (*p->width)(p, c);
913698f87a4SGarrett D'Amore }
91495c635efSGarrett D'Amore 
91595c635efSGarrett D'Amore size_t
term_strlen(const struct termp * p,const char * cp)91695c635efSGarrett D'Amore term_strlen(const struct termp *p, const char *cp)
91795c635efSGarrett D'Amore {
91895c635efSGarrett D'Amore 	size_t		 sz, rsz, i;
919260e9a87SYuri Pankov 	int		 ssz, skip, uc;
92095c635efSGarrett D'Amore 	const char	*seq, *rhs;
92195c635efSGarrett D'Amore 	enum mandoc_esc	 esc;
922260e9a87SYuri Pankov 	static const char rej[] = { '\\', ASCII_NBRSP, ASCII_HYPH,
923260e9a87SYuri Pankov 			ASCII_BREAK, '\0' };
92495c635efSGarrett D'Amore 
92595c635efSGarrett D'Amore 	/*
92695c635efSGarrett D'Amore 	 * Account for escaped sequences within string length
92795c635efSGarrett D'Amore 	 * calculations.  This follows the logic in term_word() as we
92895c635efSGarrett D'Amore 	 * must calculate the width of produced strings.
92995c635efSGarrett D'Amore 	 */
93095c635efSGarrett D'Amore 
93195c635efSGarrett D'Amore 	sz = 0;
932698f87a4SGarrett D'Amore 	skip = 0;
93395c635efSGarrett D'Amore 	while ('\0' != *cp) {
93495c635efSGarrett D'Amore 		rsz = strcspn(cp, rej);
93595c635efSGarrett D'Amore 		for (i = 0; i < rsz; i++)
936698f87a4SGarrett D'Amore 			sz += cond_width(p, *cp++, &skip);
93795c635efSGarrett D'Amore 
93895c635efSGarrett D'Amore 		switch (*cp) {
939260e9a87SYuri Pankov 		case '\\':
94095c635efSGarrett D'Amore 			cp++;
94195c635efSGarrett D'Amore 			rhs = NULL;
942cec8643bSMichal Nowak 			esc = mandoc_escape(&cp, &seq, &ssz);
94395c635efSGarrett D'Amore 			switch (esc) {
944260e9a87SYuri Pankov 			case ESCAPE_UNICODE:
945260e9a87SYuri Pankov 				uc = mchars_num2uc(seq + 1, ssz - 1);
94695c635efSGarrett D'Amore 				break;
947260e9a87SYuri Pankov 			case ESCAPE_NUMBERED:
948260e9a87SYuri Pankov 				uc = mchars_num2char(seq, ssz);
949260e9a87SYuri Pankov 				if (uc < 0)
950260e9a87SYuri Pankov 					continue;
95195c635efSGarrett D'Amore 				break;
952260e9a87SYuri Pankov 			case ESCAPE_SPECIAL:
953260e9a87SYuri Pankov 				if (p->enc == TERMENC_ASCII) {
954371584c2SYuri Pankov 					rhs = mchars_spec2str(seq, ssz, &rsz);
955260e9a87SYuri Pankov 					if (rhs != NULL)
95695c635efSGarrett D'Amore 						break;
957260e9a87SYuri Pankov 				} else {
958371584c2SYuri Pankov 					uc = mchars_spec2cp(seq, ssz);
959260e9a87SYuri Pankov 					if (uc > 0)
960260e9a87SYuri Pankov 						sz += cond_width(p, uc, &skip);
961260e9a87SYuri Pankov 				}
962260e9a87SYuri Pankov 				continue;
963cec8643bSMichal Nowak 			case ESCAPE_UNDEF:
964cec8643bSMichal Nowak 				uc = *seq;
965cec8643bSMichal Nowak 				break;
966cec8643bSMichal Nowak 			case ESCAPE_DEVICE:
967cec8643bSMichal Nowak 				if (p->type == TERMTYPE_PDF) {
968cec8643bSMichal Nowak 					rhs = "pdf";
969cec8643bSMichal Nowak 					rsz = 3;
970cec8643bSMichal Nowak 				} else if (p->type == TERMTYPE_PS) {
971cec8643bSMichal Nowak 					rhs = "ps";
972cec8643bSMichal Nowak 					rsz = 2;
973cec8643bSMichal Nowak 				} else if (p->enc == TERMENC_ASCII) {
974cec8643bSMichal Nowak 					rhs = "ascii";
975cec8643bSMichal Nowak 					rsz = 5;
976cec8643bSMichal Nowak 				} else {
977cec8643bSMichal Nowak 					rhs = "utf8";
978cec8643bSMichal Nowak 					rsz = 4;
979cec8643bSMichal Nowak 				}
980cec8643bSMichal Nowak 				break;
981260e9a87SYuri Pankov 			case ESCAPE_SKIPCHAR:
982698f87a4SGarrett D'Amore 				skip = 1;
983260e9a87SYuri Pankov 				continue;
984260e9a87SYuri Pankov 			case ESCAPE_OVERSTRIKE:
985260e9a87SYuri Pankov 				rsz = 0;
986260e9a87SYuri Pankov 				rhs = seq + ssz;
987260e9a87SYuri Pankov 				while (seq < rhs) {
988260e9a87SYuri Pankov 					if (*seq == '\\') {
989260e9a87SYuri Pankov 						mandoc_escape(&seq, NULL, NULL);
990260e9a87SYuri Pankov 						continue;
991260e9a87SYuri Pankov 					}
992260e9a87SYuri Pankov 					i = (*p->width)(p, *seq++);
993260e9a87SYuri Pankov 					if (rsz < i)
994260e9a87SYuri Pankov 						rsz = i;
995260e9a87SYuri Pankov 				}
996260e9a87SYuri Pankov 				sz += rsz;
997260e9a87SYuri Pankov 				continue;
99895c635efSGarrett D'Amore 			default:
999260e9a87SYuri Pankov 				continue;
100095c635efSGarrett D'Amore 			}
100195c635efSGarrett D'Amore 
1002260e9a87SYuri Pankov 			/*
1003260e9a87SYuri Pankov 			 * Common handling for Unicode and numbered
1004260e9a87SYuri Pankov 			 * character escape sequences.
1005260e9a87SYuri Pankov 			 */
1006260e9a87SYuri Pankov 
1007260e9a87SYuri Pankov 			if (rhs == NULL) {
1008260e9a87SYuri Pankov 				if (p->enc == TERMENC_ASCII) {
1009260e9a87SYuri Pankov 					rhs = ascii_uc2str(uc);
1010260e9a87SYuri Pankov 					rsz = strlen(rhs);
1011260e9a87SYuri Pankov 				} else {
1012260e9a87SYuri Pankov 					if ((uc < 0x20 && uc != 0x09) ||
1013260e9a87SYuri Pankov 					    (uc > 0x7E && uc < 0xA0))
1014260e9a87SYuri Pankov 						uc = 0xFFFD;
1015260e9a87SYuri Pankov 					sz += cond_width(p, uc, &skip);
1016260e9a87SYuri Pankov 					continue;
1017260e9a87SYuri Pankov 				}
1018260e9a87SYuri Pankov 			}
101995c635efSGarrett D'Amore 
1020698f87a4SGarrett D'Amore 			if (skip) {
1021698f87a4SGarrett D'Amore 				skip = 0;
1022698f87a4SGarrett D'Amore 				break;
1023698f87a4SGarrett D'Amore 			}
1024698f87a4SGarrett D'Amore 
1025260e9a87SYuri Pankov 			/*
1026260e9a87SYuri Pankov 			 * Common handling for all escape sequences
1027260e9a87SYuri Pankov 			 * printing more than one character.
1028260e9a87SYuri Pankov 			 */
1029260e9a87SYuri Pankov 
103095c635efSGarrett D'Amore 			for (i = 0; i < rsz; i++)
103195c635efSGarrett D'Amore 				sz += (*p->width)(p, *rhs++);
103295c635efSGarrett D'Amore 			break;
1033260e9a87SYuri Pankov 		case ASCII_NBRSP:
1034698f87a4SGarrett D'Amore 			sz += cond_width(p, ' ', &skip);
103595c635efSGarrett D'Amore 			cp++;
103695c635efSGarrett D'Amore 			break;
1037260e9a87SYuri Pankov 		case ASCII_HYPH:
1038698f87a4SGarrett D'Amore 			sz += cond_width(p, '-', &skip);
103995c635efSGarrett D'Amore 			cp++;
104095c635efSGarrett D'Amore 			break;
104195c635efSGarrett D'Amore 		default:
104295c635efSGarrett D'Amore 			break;
104395c635efSGarrett D'Amore 		}
104495c635efSGarrett D'Amore 	}
104595c635efSGarrett D'Amore 
1046371584c2SYuri Pankov 	return sz;
104795c635efSGarrett D'Amore }
104895c635efSGarrett D'Amore 
1049260e9a87SYuri Pankov int
term_vspan(const struct termp * p,const struct roffsu * su)105095c635efSGarrett D'Amore term_vspan(const struct termp *p, const struct roffsu *su)
105195c635efSGarrett D'Amore {
105295c635efSGarrett D'Amore 	double		 r;
1053260e9a87SYuri Pankov 	int		 ri;
105495c635efSGarrett D'Amore 
105595c635efSGarrett D'Amore 	switch (su->unit) {
1056260e9a87SYuri Pankov 	case SCALE_BU:
1057260e9a87SYuri Pankov 		r = su->scale / 40.0;
105895c635efSGarrett D'Amore 		break;
1059260e9a87SYuri Pankov 	case SCALE_CM:
1060260e9a87SYuri Pankov 		r = su->scale * 6.0 / 2.54;
106195c635efSGarrett D'Amore 		break;
1062260e9a87SYuri Pankov 	case SCALE_FS:
1063260e9a87SYuri Pankov 		r = su->scale * 65536.0 / 40.0;
1064260e9a87SYuri Pankov 		break;
1065260e9a87SYuri Pankov 	case SCALE_IN:
1066260e9a87SYuri Pankov 		r = su->scale * 6.0;
1067260e9a87SYuri Pankov 		break;
1068260e9a87SYuri Pankov 	case SCALE_MM:
1069260e9a87SYuri Pankov 		r = su->scale * 0.006;
1070260e9a87SYuri Pankov 		break;
1071260e9a87SYuri Pankov 	case SCALE_PC:
107295c635efSGarrett D'Amore 		r = su->scale;
107395c635efSGarrett D'Amore 		break;
1074260e9a87SYuri Pankov 	case SCALE_PT:
1075260e9a87SYuri Pankov 		r = su->scale / 12.0;
107695c635efSGarrett D'Amore 		break;
1077260e9a87SYuri Pankov 	case SCALE_EN:
1078260e9a87SYuri Pankov 	case SCALE_EM:
1079260e9a87SYuri Pankov 		r = su->scale * 0.6;
108095c635efSGarrett D'Amore 		break;
1081260e9a87SYuri Pankov 	case SCALE_VS:
108295c635efSGarrett D'Amore 		r = su->scale;
108395c635efSGarrett D'Amore 		break;
108495c635efSGarrett D'Amore 	default:
1085260e9a87SYuri Pankov 		abort();
1086260e9a87SYuri Pankov 	}
1087260e9a87SYuri Pankov 	ri = r > 0.0 ? r + 0.4995 : r - 0.4995;
1088371584c2SYuri Pankov 	return ri < 66 ? ri : 1;
108995c635efSGarrett D'Amore }
109095c635efSGarrett D'Amore 
1091371584c2SYuri Pankov /*
1092c66b8046SYuri Pankov  * Convert a scaling width to basic units, rounding towards 0.
1093371584c2SYuri Pankov  */
1094260e9a87SYuri Pankov int
term_hspan(const struct termp * p,const struct roffsu * su)109595c635efSGarrett D'Amore term_hspan(const struct termp *p, const struct roffsu *su)
109695c635efSGarrett D'Amore {
109795c635efSGarrett D'Amore 
1098371584c2SYuri Pankov 	return (*p->hspan)(p, su);
109995c635efSGarrett D'Amore }
1100c66b8046SYuri Pankov 
1101c66b8046SYuri Pankov /*
1102c66b8046SYuri Pankov  * Convert a scaling width to basic units, rounding to closest.
1103c66b8046SYuri Pankov  */
1104c66b8046SYuri Pankov int
term_hen(const struct termp * p,const struct roffsu * su)1105c66b8046SYuri Pankov term_hen(const struct termp *p, const struct roffsu *su)
1106c66b8046SYuri Pankov {
1107c66b8046SYuri Pankov 	int bu;
1108c66b8046SYuri Pankov 
1109c66b8046SYuri Pankov 	if ((bu = (*p->hspan)(p, su)) >= 0)
1110c66b8046SYuri Pankov 		return (bu + 11) / 24;
1111c66b8046SYuri Pankov 	else
1112c66b8046SYuri Pankov 		return -((-bu + 11) / 24);
1113c66b8046SYuri Pankov }
1114