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