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