1 /* $Id: term.c,v 1.280 2019/01/15 12:16:18 schwarze Exp $ */
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010-2019 Ingo Schwarze <schwarze@openbsd.org>
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 #include <sys/types.h>
30 #include "mandoc_aux.h"
35 static size_t cond_width(const struct termp *, int, int *);
36 static void adjbuf(struct termp_col *, size_t);
37 static void bufferc(struct termp *, char);
38 static void encode(struct termp *, const char *, size_t);
39 static void encode1(struct termp *, int);
40 static void endline(struct termp *);
41 static void term_field(struct termp *, size_t, size_t,
43 static void term_fill(struct termp *, size_t *, size_t *,
48 term_setcol(struct termp *p, size_t maxtcol)
50 if (maxtcol > p->maxtcol) {
51 p->tcols = mandoc_recallocarray(p->tcols,
52 p->maxtcol, maxtcol, sizeof(*p->tcols));
55 p->lasttcol = maxtcol - 1;
60 term_free(struct termp *p)
62 for (p->tcol = p->tcols; p->tcol < p->tcols + p->maxtcol; p->tcol++)
70 term_begin(struct termp *p, term_margin head,
71 term_margin foot, const struct roff_meta *arg)
81 term_end(struct termp *p)
88 * Flush a chunk of text. By default, break the output line each time
89 * the right margin is reached, and continue output on the next line
90 * at the same offset as the chunk itself. By default, also break the
91 * output line at the end of the chunk. There are many flags modifying
92 * this behaviour, see the comments in the body of the function.
95 term_flushln(struct termp *p)
97 size_t vbl; /* Number of blanks to prepend to the output. */
98 size_t vbr; /* Actual visual position of the end of field. */
99 size_t vfield; /* Desired visual field width. */
100 size_t vtarget; /* Desired visual position of the right margin. */
101 size_t ic; /* Character position in the input buffer. */
102 size_t nbr; /* Number of characters to print in this field. */
105 * Normally, start writing at the left margin, but with the
106 * NOPAD flag, start writing at the current position instead.
109 vbl = (p->flags & TERMP_NOPAD) || p->tcol->offset < p->viscol ?
110 0 : p->tcol->offset - p->viscol;
111 if (p->minbl && vbl < p->minbl)
114 if ((p->flags & TERMP_MULTICOL) == 0)
117 /* Loop over output lines. */
120 vfield = p->tcol->rmargin > p->viscol + vbl ?
121 p->tcol->rmargin - p->viscol - vbl : 0;
124 * Normally, break the line at the the right margin
125 * of the field, but with the NOBREAK flag, only
126 * break it at the max right margin of the screen,
127 * and with the BRNEVER flag, never break it at all.
130 vtarget = p->flags & TERMP_BRNEVER ? SIZE_MAX :
131 (p->flags & TERMP_NOBREAK) == 0 ? vfield :
132 p->maxrmargin > p->viscol + vbl ?
133 p->maxrmargin - p->viscol - vbl : 0;
136 * Figure out how much text will fit in the field.
137 * If there is whitespace only, print nothing.
140 term_fill(p, &nbr, &vbr, vtarget);
145 * With the CENTER or RIGHT flag, increase the indentation
146 * to center the text between the left and right margins
147 * or to adjust it to the right margin, respectively.
151 if (p->flags & TERMP_CENTER)
152 vbl += (vtarget - vbr) / 2;
153 else if (p->flags & TERMP_RIGHT)
154 vbl += vtarget - vbr;
157 /* Finally, print the field content. */
159 term_field(p, vbl, nbr, vbr, vtarget);
162 * If there is no text left in the field, exit the loop.
163 * If the BRTRSP flag is set, consider trailing
164 * whitespace significant when deciding whether
165 * the field fits or not.
168 for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
169 switch (p->tcol->buf[ic]) {
171 if (p->flags & TERMP_BRTRSP)
172 vbr = term_tab_next(vbr);
175 if (p->flags & TERMP_BRTRSP)
176 vbr += (*p->width)(p, ' ');
186 if (ic == p->tcol->lastcol)
190 * At the location of an automtic line break, input
191 * space characters are consumed by the line break.
194 while (p->tcol->col < p->tcol->lastcol &&
195 p->tcol->buf[p->tcol->col] == ' ')
199 * In multi-column mode, leave the rest of the text
200 * in the buffer to be handled by a subsequent
201 * invocation, such that the other columns of the
202 * table can be handled first.
203 * In single-column mode, simply break the line.
206 if (p->flags & TERMP_MULTICOL)
213 * Normally, start the next line at the same indentation
214 * as this one, but with the BRIND flag, start it at the
215 * right margin instead. This is used together with
216 * NOBREAK for the tags in various kinds of tagged lists.
219 vbl = p->flags & TERMP_BRIND ?
220 p->tcol->rmargin : p->tcol->offset;
223 /* Reset output state in preparation for the next field. */
225 p->col = p->tcol->col = p->tcol->lastcol = 0;
226 p->minbl = p->trailspace;
227 p->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE | TERMP_NOPAD);
229 if (p->flags & TERMP_MULTICOL)
233 * The HANG flag means that the next field
234 * always follows on the same line.
235 * The NOBREAK flag means that the next field
236 * follows on the same line unless the field was overrun.
237 * Normally, break the line at the end of each field.
240 if ((p->flags & TERMP_HANG) == 0 &&
241 ((p->flags & TERMP_NOBREAK) == 0 ||
242 vbr + term_len(p, p->trailspace) > vfield))
247 * Store the number of input characters to print in this field in *nbr
248 * and their total visual width to print in *vbr.
249 * If there is only whitespace in the field, both remain zero.
250 * The desired visual width of the field is provided by vtarget.
251 * If the first word is longer, the field will be overrun.
254 term_fill(struct termp *p, size_t *nbr, size_t *vbr, size_t vtarget)
256 size_t ic; /* Character position in the input buffer. */
257 size_t vis; /* Visual position of the current character. */
258 size_t vn; /* Visual position of the next character. */
259 int breakline; /* Break at the end of this word. */
260 int graph; /* Last character was non-blank. */
262 *nbr = *vbr = vis = 0;
263 breakline = graph = 0;
264 for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
265 switch (p->tcol->buf[ic]) {
266 case '\b': /* Escape \o (overstrike) or backspace markup. */
268 vis -= (*p->width)(p, p->tcol->buf[ic - 1]);
271 case '\t': /* Normal ASCII whitespace. */
273 case ASCII_BREAK: /* Escape \: (breakpoint). */
274 switch (p->tcol->buf[ic]) {
276 vn = term_tab_next(vis);
279 vn = vis + (*p->width)(p, ' ');
285 /* Can break at the end of a word. */
286 if (breakline || vn > vtarget)
296 case '\n': /* Escape \p (break at the end of the word). */
300 case ASCII_HYPH: /* Breakable hyphen. */
303 * We are about to decide whether to break the
304 * line or not, so we no longer need this hyphen
305 * to be marked as breakable. Put back a real
306 * hyphen such that we get the correct width.
308 p->tcol->buf[ic] = '-';
309 vis += (*p->width)(p, '-');
318 case ASCII_NBRSP: /* Non-breakable space. */
319 p->tcol->buf[ic] = ' ';
321 default: /* Printable character. */
323 vis += (*p->width)(p, p->tcol->buf[ic]);
324 if (vis > vtarget && *nbr > 0)
332 * If the last word extends to the end of the field without any
333 * trailing whitespace, the loop could not check yet whether it
334 * can remain on this line. So do the check now.
337 if (graph && (vis <= vtarget || *nbr == 0)) {
344 * Print the contents of one field
345 * with an indentation of vbl visual columns,
346 * an input string length of nbr characters,
347 * an output width of vbr visual columns,
348 * and a desired field width of vtarget visual columns.
351 term_field(struct termp *p, size_t vbl, size_t nbr, size_t vbr, size_t vtarget)
353 size_t ic; /* Character position in the input buffer. */
354 size_t vis; /* Visual position of the current character. */
355 size_t dv; /* Visual width of the current character. */
356 size_t vn; /* Visual position of the next character. */
359 for (ic = p->tcol->col; ic < nbr; ic++) {
362 * To avoid the printing of trailing whitespace,
363 * do not print whitespace right away, only count it.
366 switch (p->tcol->buf[ic]) {
371 vn = term_tab_next(vis);
377 dv = (*p->width)(p, ' ');
386 * We found a non-blank character to print,
387 * so write preceding white space now.
391 (*p->advance)(p, vbl);
396 /* Print the character and adjust the visual position. */
398 (*p->letter)(p, p->tcol->buf[ic]);
399 if (p->tcol->buf[ic] == '\b') {
400 dv = (*p->width)(p, p->tcol->buf[ic - 1]);
404 dv = (*p->width)(p, p->tcol->buf[ic]);
413 endline(struct termp *p)
415 if ((p->flags & (TERMP_NEWMC | TERMP_ENDMC)) == TERMP_ENDMC) {
417 p->flags &= ~TERMP_ENDMC;
420 if (p->viscol && p->maxrmargin >= p->viscol)
421 (*p->advance)(p, p->maxrmargin - p->viscol + 1);
422 p->flags |= TERMP_NOBUF | TERMP_NOSPACE;
424 p->flags &= ~(TERMP_NOBUF | TERMP_NEWMC);
432 * A newline only breaks an existing line; it won't assert vertical
433 * space. All data in the output buffer is flushed prior to the newline
437 term_newln(struct termp *p)
440 p->flags |= TERMP_NOSPACE;
441 if (p->tcol->lastcol || p->viscol)
446 * Asserts a vertical space (a full, empty line-break between lines).
447 * Note that if used twice, this will cause two blank spaces and so on.
448 * All data in the output buffer is flushed prior to the newline
452 term_vspace(struct termp *p)
464 /* Swap current and previous font; for \fP and .ft P */
466 term_fontlast(struct termp *p)
471 p->fontl = p->fontq[p->fonti];
472 p->fontq[p->fonti] = f;
475 /* Set font, save current, discard previous; for \f, .ft, .B etc. */
477 term_fontrepl(struct termp *p, enum termfont f)
480 p->fontl = p->fontq[p->fonti];
481 p->fontq[p->fonti] = f;
484 /* Set font, save previous. */
486 term_fontpush(struct termp *p, enum termfont f)
489 p->fontl = p->fontq[p->fonti];
490 if (++p->fonti == p->fontsz) {
492 p->fontq = mandoc_reallocarray(p->fontq,
493 p->fontsz, sizeof(*p->fontq));
495 p->fontq[p->fonti] = f;
498 /* Flush to make the saved pointer current again. */
500 term_fontpopq(struct termp *p, int i)
508 /* Pop one font off the stack. */
510 term_fontpop(struct termp *p)
518 * Handle pwords, partial words, which may be either a single word or a
519 * phrase that cannot be broken down (such as a literal string). This
520 * handles word styling.
523 term_word(struct termp *p, const char *word)
526 const char nbrsp[2] = { ASCII_NBRSP, 0 };
527 const char *seq, *cp;
529 size_t csz, lsz, ssz;
532 if ((p->flags & TERMP_NOBUF) == 0) {
533 if ((p->flags & TERMP_NOSPACE) == 0) {
534 if ((p->flags & TERMP_KEEP) == 0) {
536 if (p->flags & TERMP_SENTENCE)
539 bufferc(p, ASCII_NBRSP);
541 if (p->flags & TERMP_PREKEEP)
542 p->flags |= TERMP_KEEP;
543 if (p->flags & TERMP_NONOSPACE)
544 p->flags |= TERMP_NOSPACE;
546 p->flags &= ~TERMP_NOSPACE;
547 p->flags &= ~(TERMP_SENTENCE | TERMP_NONEWLINE);
551 while ('\0' != *word) {
553 if (TERMP_NBRWORD & p->flags) {
559 ssz = strcspn(word, "\\ ");
561 ssz = strcspn(word, "\\");
562 encode(p, word, ssz);
568 esc = mandoc_escape(&word, &seq, &sz);
571 uc = mchars_num2uc(seq + 1, sz - 1);
573 case ESCAPE_NUMBERED:
574 uc = mchars_num2char(seq, sz);
579 if (p->enc == TERMENC_ASCII) {
580 cp = mchars_spec2str(seq, sz, &ssz);
584 uc = mchars_spec2cp(seq, sz);
592 case ESCAPE_FONTBOLD:
593 term_fontrepl(p, TERMFONT_BOLD);
595 case ESCAPE_FONTITALIC:
596 term_fontrepl(p, TERMFONT_UNDER);
599 term_fontrepl(p, TERMFONT_BI);
603 case ESCAPE_FONTROMAN:
604 term_fontrepl(p, TERMFONT_NONE);
606 case ESCAPE_FONTPREV:
613 if (p->flags & TERMP_BACKAFTER)
614 p->flags &= ~TERMP_BACKAFTER;
615 else if (*word == '\0')
616 p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE);
619 if (p->type == TERMTYPE_PDF)
621 else if (p->type == TERMTYPE_PS)
623 else if (p->enc == TERMENC_ASCII)
624 encode(p, "ascii", 5);
626 encode(p, "utf8", 4);
634 if (a2roffsu(seq, &su, SCALE_EM) == NULL)
636 uc += term_hen(p, &su);
639 bufferc(p, ASCII_NBRSP);
640 else if (p->col > (size_t)(-uc))
645 if (p->tcol->offset > (size_t)(-uc)) {
647 p->tcol->offset += uc;
649 p->ti -= p->tcol->offset;
655 if ((cp = a2roffsu(seq, &su, SCALE_EM)) == NULL)
657 uc = term_hen(p, &su);
659 if (p->tcol->rmargin <= p->tcol->offset)
661 lsz = p->tcol->rmargin - p->tcol->offset;
666 else if (*cp == '\\') {
668 esc = mandoc_escape(&seq, &cp, &sz);
671 uc = mchars_num2uc(cp + 1, sz - 1);
673 case ESCAPE_NUMBERED:
674 uc = mchars_num2char(cp, sz);
677 uc = mchars_spec2cp(cp, sz);
688 if (uc < 0x20 || (uc > 0x7E && uc < 0xA0))
690 if (p->enc == TERMENC_ASCII) {
691 cp = ascii_uc2str(uc);
692 csz = term_strlen(p, cp);
695 csz = (*p->width)(p, uc);
697 if (p->enc == TERMENC_ASCII)
704 case ESCAPE_SKIPCHAR:
705 p->flags |= TERMP_BACKAFTER;
707 case ESCAPE_OVERSTRIKE:
711 mandoc_escape(&seq, NULL, NULL);
716 if (p->flags & TERMP_BACKBEFORE)
717 p->flags |= TERMP_BACKAFTER;
719 p->flags |= TERMP_BACKBEFORE;
722 /* Trim trailing backspace/blank pair. */
723 if (p->tcol->lastcol > 2 &&
724 (p->tcol->buf[p->tcol->lastcol - 1] == ' ' ||
725 p->tcol->buf[p->tcol->lastcol - 1] == '\t'))
726 p->tcol->lastcol -= 2;
727 if (p->col > p->tcol->lastcol)
728 p->col = p->tcol->lastcol;
735 * Common handling for Unicode and numbered
736 * character escape sequences.
739 if (p->enc == TERMENC_ASCII) {
740 cp = ascii_uc2str(uc);
741 encode(p, cp, strlen(cp));
743 if ((uc < 0x20 && uc != 0x09) ||
744 (uc > 0x7E && uc < 0xA0))
749 p->flags &= ~TERMP_NBRWORD;
753 adjbuf(struct termp_col *c, size_t sz)
757 while (c->maxcols <= sz)
759 c->buf = mandoc_reallocarray(c->buf, c->maxcols, sizeof(*c->buf));
763 bufferc(struct termp *p, char c)
765 if (p->flags & TERMP_NOBUF) {
769 if (p->col + 1 >= p->tcol->maxcols)
770 adjbuf(p->tcol, p->col + 1);
771 if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
772 p->tcol->buf[p->col] = c;
773 if (p->tcol->lastcol < ++p->col)
774 p->tcol->lastcol = p->col;
779 * Do this for a single (probably unicode) value.
780 * Does not check for non-decorated glyphs.
783 encode1(struct termp *p, int c)
787 if (p->flags & TERMP_NOBUF) {
792 if (p->col + 7 >= p->tcol->maxcols)
793 adjbuf(p->tcol, p->col + 7);
795 f = (c == ASCII_HYPH || c > 127 || isgraph(c)) ?
796 p->fontq[p->fonti] : TERMFONT_NONE;
798 if (p->flags & TERMP_BACKBEFORE) {
799 if (p->tcol->buf[p->col - 1] == ' ' ||
800 p->tcol->buf[p->col - 1] == '\t')
803 p->tcol->buf[p->col++] = '\b';
804 p->flags &= ~TERMP_BACKBEFORE;
806 if (f == TERMFONT_UNDER || f == TERMFONT_BI) {
807 p->tcol->buf[p->col++] = '_';
808 p->tcol->buf[p->col++] = '\b';
810 if (f == TERMFONT_BOLD || f == TERMFONT_BI) {
812 p->tcol->buf[p->col++] = '-';
814 p->tcol->buf[p->col++] = c;
815 p->tcol->buf[p->col++] = '\b';
817 if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
818 p->tcol->buf[p->col] = c;
819 if (p->tcol->lastcol < ++p->col)
820 p->tcol->lastcol = p->col;
821 if (p->flags & TERMP_BACKAFTER) {
822 p->flags |= TERMP_BACKBEFORE;
823 p->flags &= ~TERMP_BACKAFTER;
828 encode(struct termp *p, const char *word, size_t sz)
832 if (p->flags & TERMP_NOBUF) {
833 for (i = 0; i < sz; i++)
834 (*p->letter)(p, word[i]);
838 if (p->col + 2 + (sz * 5) >= p->tcol->maxcols)
839 adjbuf(p->tcol, p->col + 2 + (sz * 5));
841 for (i = 0; i < sz; i++) {
842 if (ASCII_HYPH == word[i] ||
843 isgraph((unsigned char)word[i]))
846 if (p->tcol->lastcol <= p->col ||
847 (word[i] != ' ' && word[i] != ASCII_NBRSP))
848 p->tcol->buf[p->col] = word[i];
852 * Postpone the effect of \z while handling
853 * an overstrike sequence from ascii_uc2str().
856 if (word[i] == '\b' &&
857 (p->flags & TERMP_BACKBEFORE)) {
858 p->flags &= ~TERMP_BACKBEFORE;
859 p->flags |= TERMP_BACKAFTER;
863 if (p->tcol->lastcol < p->col)
864 p->tcol->lastcol = p->col;
868 term_setwidth(struct termp *p, const char *wstr)
888 if (a2roffsu(wstr, &su, SCALE_MAX) != NULL)
889 width = term_hspan(p, &su);
893 (*p->setwidth)(p, iop, width);
897 term_len(const struct termp *p, size_t sz)
900 return (*p->width)(p, ' ') * sz;
904 cond_width(const struct termp *p, int c, int *skip)
911 return (*p->width)(p, c);
915 term_strlen(const struct termp *p, const char *cp)
919 const char *seq, *rhs;
921 static const char rej[] = { '\\', ASCII_NBRSP, ASCII_HYPH,
925 * Account for escaped sequences within string length
926 * calculations. This follows the logic in term_word() as we
927 * must calculate the width of produced strings.
932 while ('\0' != *cp) {
933 rsz = strcspn(cp, rej);
934 for (i = 0; i < rsz; i++)
935 sz += cond_width(p, *cp++, &skip);
941 esc = mandoc_escape(&cp, &seq, &ssz);
944 uc = mchars_num2uc(seq + 1, ssz - 1);
946 case ESCAPE_NUMBERED:
947 uc = mchars_num2char(seq, ssz);
952 if (p->enc == TERMENC_ASCII) {
953 rhs = mchars_spec2str(seq, ssz, &rsz);
957 uc = mchars_spec2cp(seq, ssz);
959 sz += cond_width(p, uc, &skip);
966 if (p->type == TERMTYPE_PDF) {
969 } else if (p->type == TERMTYPE_PS) {
972 } else if (p->enc == TERMENC_ASCII) {
980 case ESCAPE_SKIPCHAR:
983 case ESCAPE_OVERSTRIKE:
988 mandoc_escape(&seq, NULL, NULL);
991 i = (*p->width)(p, *seq++);
1002 * Common handling for Unicode and numbered
1003 * character escape sequences.
1007 if (p->enc == TERMENC_ASCII) {
1008 rhs = ascii_uc2str(uc);
1011 if ((uc < 0x20 && uc != 0x09) ||
1012 (uc > 0x7E && uc < 0xA0))
1014 sz += cond_width(p, uc, &skip);
1025 * Common handling for all escape sequences
1026 * printing more than one character.
1029 for (i = 0; i < rsz; i++)
1030 sz += (*p->width)(p, *rhs++);
1033 sz += cond_width(p, ' ', &skip);
1037 sz += cond_width(p, '-', &skip);
1049 term_vspan(const struct termp *p, const struct roffsu *su)
1056 r = su->scale / 40.0;
1059 r = su->scale * 6.0 / 2.54;
1062 r = su->scale * 65536.0 / 40.0;
1065 r = su->scale * 6.0;
1068 r = su->scale * 0.006;
1074 r = su->scale / 12.0;
1078 r = su->scale * 0.6;
1086 ri = r > 0.0 ? r + 0.4995 : r - 0.4995;
1087 return ri < 66 ? ri : 1;
1091 * Convert a scaling width to basic units, rounding towards 0.
1094 term_hspan(const struct termp *p, const struct roffsu *su)
1097 return (*p->hspan)(p, su);
1101 * Convert a scaling width to basic units, rounding to closest.
1104 term_hen(const struct termp *p, const struct roffsu *su)
1108 if ((bu = (*p->hspan)(p, su)) >= 0)
1109 return (bu + 11) / 24;
1111 return -((-bu + 11) / 24);