Annotation of src/usr.bin/mandoc/term.c, Revision 1.128
1.128 ! schwarze 1: /* $OpenBSD: term.c,v 1.127 2017/06/07 20:01:07 schwarze Exp $ */
1.1 kristaps 2: /*
1.59 schwarze 3: * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
1.119 schwarze 4: * Copyright (c) 2010-2017 Ingo Schwarze <schwarze@openbsd.org>
1.1 kristaps 5: *
6: * Permission to use, copy, modify, and distribute this software for any
1.2 schwarze 7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
1.1 kristaps 9: *
1.106 schwarze 10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1.2 schwarze 11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1.106 schwarze 12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1.2 schwarze 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.
1.1 kristaps 17: */
1.20 schwarze 18: #include <sys/types.h>
19:
1.1 kristaps 20: #include <assert.h>
1.20 schwarze 21: #include <ctype.h>
1.1 kristaps 22: #include <stdio.h>
23: #include <stdlib.h>
24: #include <string.h>
25:
1.34 schwarze 26: #include "mandoc.h"
1.79 schwarze 27: #include "mandoc_aux.h"
1.16 schwarze 28: #include "out.h"
1.1 kristaps 29: #include "term.h"
1.16 schwarze 30: #include "main.h"
1.1 kristaps 31:
1.64 schwarze 32: static size_t cond_width(const struct termp *, int, int *);
1.126 schwarze 33: static void adjbuf(struct termp_col *, size_t);
1.59 schwarze 34: static void bufferc(struct termp *, char);
35: static void encode(struct termp *, const char *, size_t);
36: static void encode1(struct termp *, int);
1.124 schwarze 37: static void endline(struct termp *);
1.1 kristaps 38:
1.83 schwarze 39:
1.37 schwarze 40: void
41: term_free(struct termp *p)
1.1 kristaps 42: {
1.126 schwarze 43: for (p->tcol = p->tcols; p->tcol < p->tcols + p->maxtcol; p->tcol++)
44: free(p->tcol->buf);
45: free(p->tcols);
1.98 schwarze 46: free(p->fontq);
1.37 schwarze 47: free(p);
1.1 kristaps 48: }
49:
1.13 schwarze 50: void
1.83 schwarze 51: term_begin(struct termp *p, term_margin head,
1.106 schwarze 52: term_margin foot, const struct roff_meta *arg)
1.1 kristaps 53: {
54:
1.37 schwarze 55: p->headf = head;
56: p->footf = foot;
57: p->argf = arg;
58: (*p->begin)(p);
1.1 kristaps 59: }
60:
1.37 schwarze 61: void
62: term_end(struct termp *p)
1.1 kristaps 63: {
64:
1.37 schwarze 65: (*p->end)(p);
1.1 kristaps 66: }
67:
68: /*
1.82 schwarze 69: * Flush a chunk of text. By default, break the output line each time
70: * the right margin is reached, and continue output on the next line
71: * at the same offset as the chunk itself. By default, also break the
72: * output line at the end of the chunk.
1.27 schwarze 73: * The following flags may be specified:
1.1 kristaps 74: *
1.82 schwarze 75: * - TERMP_NOBREAK: Do not break the output line at the right margin,
76: * but only at the max right margin. Also, do not break the output
77: * line at the end of the chunk, such that the next call can pad to
78: * the next column. However, if less than p->trailspace blanks,
79: * which can be 0, 1, or 2, remain to the right margin, the line
80: * will be broken.
1.110 schwarze 81: * - TERMP_BRTRSP: Consider trailing whitespace significant
82: * when deciding whether the chunk fits or not.
1.82 schwarze 83: * - TERMP_BRIND: If the chunk does not fit and the output line has
84: * to be broken, start the next line at the right margin instead
85: * of at the offset. Used together with TERMP_NOBREAK for the tags
86: * in various kinds of tagged lists.
1.123 schwarze 87: * - TERMP_HANG: Do not break the output line at the right margin,
1.82 schwarze 88: * append the next chunk after it even if this one is too long.
89: * To be used together with TERMP_NOBREAK.
1.123 schwarze 90: * - TERMP_NOPAD: Start writing at the current position,
91: * do not pad with blank characters up to the offset.
1.1 kristaps 92: */
93: void
94: term_flushln(struct termp *p)
95: {
1.19 schwarze 96: size_t vis; /* current visual position on output */
97: size_t vbl; /* number of blanks to prepend to output */
1.33 schwarze 98: size_t vend; /* end of word visual position on output */
1.19 schwarze 99: size_t bp; /* visual right border position */
1.51 schwarze 100: size_t dv; /* temporary for visual pos calculations */
1.126 schwarze 101: size_t j; /* temporary loop index for p->tcol->buf */
1.71 schwarze 102: size_t jhy; /* last hyph before overflow w/r/t j */
1.42 schwarze 103: size_t maxvis; /* output position of visible boundary */
1.126 schwarze 104: int ntab; /* number of tabs to prepend */
1.1 kristaps 105:
1.126 schwarze 106: vbl = (p->flags & TERMP_NOPAD) || p->tcol->offset < p->viscol ?
107: 0 : p->tcol->offset - p->viscol;
1.123 schwarze 108: if (p->minbl && vbl < p->minbl)
109: vbl = p->minbl;
1.126 schwarze 110: maxvis = p->tcol->rmargin > p->viscol + vbl ?
111: p->tcol->rmargin - p->viscol - vbl : 0;
1.123 schwarze 112: bp = !(p->flags & TERMP_NOBREAK) ? maxvis :
113: p->maxrmargin > p->viscol + vbl ?
114: p->maxrmargin - p->viscol - vbl : 0;
1.53 schwarze 115: vis = vend = 0;
1.19 schwarze 116:
1.127 schwarze 117: if (p->lasttcol == 0)
118: p->tcol->col = 0;
119: while (p->tcol->col < p->lastcol) {
120:
1.22 schwarze 121: /*
1.42 schwarze 122: * Handle literal tab characters: collapse all
123: * subsequent tabs into a single huge set of spaces.
1.30 schwarze 124: */
1.127 schwarze 125:
1.66 schwarze 126: ntab = 0;
1.127 schwarze 127: while (p->tcol->col < p->lastcol &&
128: p->tcol->buf[p->tcol->col] == '\t') {
1.120 schwarze 129: vend = term_tab_next(vis);
1.30 schwarze 130: vbl += vend - vis;
131: vis = vend;
1.66 schwarze 132: ntab++;
1.127 schwarze 133: p->tcol->col++;
1.30 schwarze 134: }
1.22 schwarze 135:
1.1 kristaps 136: /*
137: * Count up visible word characters. Control sequences
138: * (starting with the CSI) aren't counted. A space
139: * generates a non-printing word, which is valid (the
140: * space is printed according to regular spacing rules).
141: */
142:
1.127 schwarze 143: jhy = 0;
144: for (j = p->tcol->col; j < p->lastcol; j++) {
1.126 schwarze 145: if (p->tcol->buf[j] == ' ' || p->tcol->buf[j] == '\t')
1.1 kristaps 146: break;
1.42 schwarze 147:
1.117 krw 148: /* Back over the last printed character. */
1.126 schwarze 149: if (p->tcol->buf[j] == '\b') {
1.42 schwarze 150: assert(j);
1.126 schwarze 151: vend -= (*p->width)(p, p->tcol->buf[j - 1]);
1.42 schwarze 152: continue;
153: }
154:
155: /* Regular word. */
156: /* Break at the hyphen point if we overrun. */
1.83 schwarze 157: if (vend > vis && vend < bp &&
1.126 schwarze 158: (p->tcol->buf[j] == ASCII_HYPH||
159: p->tcol->buf[j] == ASCII_BREAK))
1.42 schwarze 160: jhy = j;
161:
1.78 schwarze 162: /*
163: * Hyphenation now decided, put back a real
164: * hyphen such that we get the correct width.
165: */
1.126 schwarze 166: if (p->tcol->buf[j] == ASCII_HYPH)
167: p->tcol->buf[j] = '-';
1.78 schwarze 168:
1.126 schwarze 169: vend += (*p->width)(p, p->tcol->buf[j]);
1.1 kristaps 170: }
171:
172: /*
1.5 schwarze 173: * Find out whether we would exceed the right margin.
1.33 schwarze 174: * If so, break to the next line.
1.5 schwarze 175: */
1.127 schwarze 176:
177: if (vend > bp && jhy == 0 && vis > 0 &&
1.124 schwarze 178: (p->flags & TERMP_BRNEVER) == 0) {
1.127 schwarze 179: if (p->lasttcol)
180: return;
181:
182: endline(p);
1.22 schwarze 183: vend -= vis;
1.66 schwarze 184:
1.120 schwarze 185: /* Use pending tabs on the new line. */
186:
187: vbl = 0;
188: while (ntab--)
189: vbl = term_tab_next(vbl);
190:
191: /* Re-establish indentation. */
1.66 schwarze 192:
1.123 schwarze 193: if (p->flags & TERMP_BRIND)
1.126 schwarze 194: vbl += p->tcol->rmargin;
1.123 schwarze 195: else
1.126 schwarze 196: vbl += p->tcol->offset;
197: maxvis = p->tcol->rmargin > vbl ?
198: p->tcol->rmargin - vbl : 0;
1.123 schwarze 199: bp = !(p->flags & TERMP_NOBREAK) ? maxvis :
200: p->maxrmargin > vbl ? p->maxrmargin - vbl : 0;
1.1 kristaps 201: }
1.30 schwarze 202:
1.127 schwarze 203: /*
204: * Write out the rest of the word.
205: */
206:
207: for ( ; p->tcol->col < p->lastcol; p->tcol->col++) {
208: if (vend > bp && jhy > 0 && p->tcol->col > jhy)
1.30 schwarze 209: break;
1.127 schwarze 210: if (p->tcol->buf[p->tcol->col] == '\t')
1.1 kristaps 211: break;
1.127 schwarze 212: if (p->tcol->buf[p->tcol->col] == ' ') {
213: j = p->tcol->col;
214: while (p->tcol->col < p->lastcol &&
215: p->tcol->buf[p->tcol->col] == ' ')
216: p->tcol->col++;
217: dv = (p->tcol->col - j) * (*p->width)(p, ' ');
1.51 schwarze 218: vbl += dv;
219: vend += dv;
1.22 schwarze 220: break;
221: }
1.127 schwarze 222: if (p->tcol->buf[p->tcol->col] == ASCII_NBRSP) {
1.42 schwarze 223: vbl += (*p->width)(p, ' ');
1.33 schwarze 224: continue;
225: }
1.127 schwarze 226: if (p->tcol->buf[p->tcol->col] == ASCII_BREAK)
1.77 schwarze 227: continue;
1.33 schwarze 228:
229: /*
230: * Now we definitely know there will be
231: * printable characters to output,
232: * so write preceding white space now.
233: */
234: if (vbl) {
1.37 schwarze 235: (*p->advance)(p, vbl);
1.33 schwarze 236: p->viscol += vbl;
237: vbl = 0;
1.61 schwarze 238: }
239:
1.127 schwarze 240: (*p->letter)(p, p->tcol->buf[p->tcol->col]);
241: if (p->tcol->buf[p->tcol->col] == '\b')
242: p->viscol -= (*p->width)(p,
243: p->tcol->buf[p->tcol->col - 1]);
1.83 schwarze 244: else
1.127 schwarze 245: p->viscol += (*p->width)(p,
246: p->tcol->buf[p->tcol->col]);
1.1 kristaps 247: }
1.22 schwarze 248: vis = vend;
1.1 kristaps 249: }
1.48 schwarze 250:
251: /*
252: * If there was trailing white space, it was not printed;
253: * so reset the cursor position accordingly.
254: */
1.127 schwarze 255:
1.95 schwarze 256: if (vis > vbl)
1.61 schwarze 257: vis -= vbl;
1.95 schwarze 258: else
259: vis = 0;
1.18 schwarze 260:
1.125 schwarze 261: p->col = p->lastcol = 0;
1.124 schwarze 262: p->minbl = p->trailspace;
1.123 schwarze 263: p->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE | TERMP_NOPAD);
1.1 kristaps 264:
1.110 schwarze 265: /* Trailing whitespace is significant in some columns. */
1.127 schwarze 266:
1.110 schwarze 267: if (vis && vbl && (TERMP_BRTRSP & p->flags))
268: vis += vbl;
1.1 kristaps 269:
1.61 schwarze 270: /* If the column was overrun, break the line. */
1.124 schwarze 271: if ((p->flags & TERMP_NOBREAK) == 0 ||
272: ((p->flags & TERMP_HANG) == 0 &&
273: vis + p->trailspace * (*p->width)(p, ' ') > maxvis))
274: endline(p);
275: }
276:
277: static void
278: endline(struct termp *p)
279: {
280: if ((p->flags & (TERMP_NEWMC | TERMP_ENDMC)) == TERMP_ENDMC) {
281: p->mc = NULL;
282: p->flags &= ~TERMP_ENDMC;
283: }
284: if (p->mc != NULL) {
285: if (p->viscol && p->maxrmargin >= p->viscol)
286: (*p->advance)(p, p->maxrmargin - p->viscol + 1);
287: p->flags |= TERMP_NOBUF | TERMP_NOSPACE;
288: term_word(p, p->mc);
289: p->flags &= ~(TERMP_NOBUF | TERMP_NEWMC);
290: }
291: p->viscol = 0;
292: p->minbl = 0;
293: (*p->endline)(p);
1.1 kristaps 294: }
295:
1.83 schwarze 296: /*
1.1 kristaps 297: * A newline only breaks an existing line; it won't assert vertical
298: * space. All data in the output buffer is flushed prior to the newline
299: * assertion.
300: */
301: void
302: term_newln(struct termp *p)
303: {
304:
305: p->flags |= TERMP_NOSPACE;
1.125 schwarze 306: if (p->lastcol || p->viscol)
1.61 schwarze 307: term_flushln(p);
1.1 kristaps 308: }
309:
310: /*
311: * Asserts a vertical space (a full, empty line-break between lines).
312: * Note that if used twice, this will cause two blank spaces and so on.
313: * All data in the output buffer is flushed prior to the newline
314: * assertion.
315: */
316: void
317: term_vspace(struct termp *p)
318: {
319:
320: term_newln(p);
1.29 schwarze 321: p->viscol = 0;
1.124 schwarze 322: p->minbl = 0;
1.63 schwarze 323: if (0 < p->skipvsp)
324: p->skipvsp--;
325: else
326: (*p->endline)(p);
1.1 kristaps 327: }
328:
1.98 schwarze 329: /* Swap current and previous font; for \fP and .ft P */
1.20 schwarze 330: void
331: term_fontlast(struct termp *p)
332: {
333: enum termfont f;
1.11 schwarze 334:
1.20 schwarze 335: f = p->fontl;
336: p->fontl = p->fontq[p->fonti];
337: p->fontq[p->fonti] = f;
338: }
339:
1.98 schwarze 340: /* Set font, save current, discard previous; for \f, .ft, .B etc. */
1.20 schwarze 341: void
342: term_fontrepl(struct termp *p, enum termfont f)
343: {
344:
345: p->fontl = p->fontq[p->fonti];
346: p->fontq[p->fonti] = f;
1.1 kristaps 347: }
348:
1.98 schwarze 349: /* Set font, save previous. */
1.20 schwarze 350: void
351: term_fontpush(struct termp *p, enum termfont f)
1.1 kristaps 352: {
1.7 schwarze 353:
1.20 schwarze 354: p->fontl = p->fontq[p->fonti];
1.98 schwarze 355: if (++p->fonti == p->fontsz) {
356: p->fontsz += 8;
357: p->fontq = mandoc_reallocarray(p->fontq,
1.116 schwarze 358: p->fontsz, sizeof(*p->fontq));
1.98 schwarze 359: }
360: p->fontq[p->fonti] = f;
1.20 schwarze 361: }
1.1 kristaps 362:
1.98 schwarze 363: /* Flush to make the saved pointer current again. */
1.20 schwarze 364: void
1.104 schwarze 365: term_fontpopq(struct termp *p, int i)
1.20 schwarze 366: {
1.1 kristaps 367:
1.104 schwarze 368: assert(i >= 0);
369: if (p->fonti > i)
370: p->fonti = i;
1.20 schwarze 371: }
1.1 kristaps 372:
1.98 schwarze 373: /* Pop one font off the stack. */
1.20 schwarze 374: void
375: term_fontpop(struct termp *p)
376: {
1.1 kristaps 377:
1.20 schwarze 378: assert(p->fonti);
379: p->fonti--;
1.1 kristaps 380: }
381:
382: /*
383: * Handle pwords, partial words, which may be either a single word or a
384: * phrase that cannot be broken down (such as a literal string). This
385: * handles word styling.
386: */
1.7 schwarze 387: void
388: term_word(struct termp *p, const char *word)
1.1 kristaps 389: {
1.121 schwarze 390: struct roffsu su;
1.75 schwarze 391: const char nbrsp[2] = { ASCII_NBRSP, 0 };
1.59 schwarze 392: const char *seq, *cp;
393: int sz, uc;
1.122 schwarze 394: size_t csz, lsz, ssz;
1.59 schwarze 395: enum mandoc_esc esc;
1.1 kristaps 396:
1.124 schwarze 397: if ((p->flags & TERMP_NOBUF) == 0) {
398: if ((p->flags & TERMP_NOSPACE) == 0) {
399: if ((p->flags & TERMP_KEEP) == 0) {
1.40 schwarze 400: bufferc(p, ' ');
1.124 schwarze 401: if (p->flags & TERMP_SENTENCE)
402: bufferc(p, ' ');
403: } else
404: bufferc(p, ASCII_NBRSP);
405: }
406: if (p->flags & TERMP_PREKEEP)
407: p->flags |= TERMP_KEEP;
408: if (p->flags & TERMP_NONOSPACE)
409: p->flags |= TERMP_NOSPACE;
410: else
411: p->flags &= ~TERMP_NOSPACE;
412: p->flags &= ~(TERMP_SENTENCE | TERMP_NONEWLINE);
413: p->skipvsp = 0;
1.31 schwarze 414: }
415:
1.59 schwarze 416: while ('\0' != *word) {
1.64 schwarze 417: if ('\\' != *word) {
1.75 schwarze 418: if (TERMP_NBRWORD & p->flags) {
419: if (' ' == *word) {
420: encode(p, nbrsp, 1);
421: word++;
422: continue;
423: }
424: ssz = strcspn(word, "\\ ");
425: } else
426: ssz = strcspn(word, "\\");
1.45 schwarze 427: encode(p, word, ssz);
1.64 schwarze 428: word += (int)ssz;
1.20 schwarze 429: continue;
1.64 schwarze 430: }
1.20 schwarze 431:
1.59 schwarze 432: word++;
433: esc = mandoc_escape(&word, &seq, &sz);
434: if (ESCAPE_ERROR == esc)
1.85 schwarze 435: continue;
1.59 schwarze 436:
437: switch (esc) {
1.83 schwarze 438: case ESCAPE_UNICODE:
1.89 schwarze 439: uc = mchars_num2uc(seq + 1, sz - 1);
1.56 schwarze 440: break;
1.83 schwarze 441: case ESCAPE_NUMBERED:
1.93 schwarze 442: uc = mchars_num2char(seq, sz);
443: if (uc < 0)
444: continue;
1.20 schwarze 445: break;
1.83 schwarze 446: case ESCAPE_SPECIAL:
1.89 schwarze 447: if (p->enc == TERMENC_ASCII) {
1.114 schwarze 448: cp = mchars_spec2str(seq, sz, &ssz);
1.92 schwarze 449: if (cp != NULL)
1.89 schwarze 450: encode(p, cp, ssz);
451: } else {
1.114 schwarze 452: uc = mchars_spec2cp(seq, sz);
1.90 schwarze 453: if (uc > 0)
454: encode1(p, uc);
1.89 schwarze 455: }
1.93 schwarze 456: continue;
1.83 schwarze 457: case ESCAPE_FONTBOLD:
1.20 schwarze 458: term_fontrepl(p, TERMFONT_BOLD);
1.93 schwarze 459: continue;
1.83 schwarze 460: case ESCAPE_FONTITALIC:
1.20 schwarze 461: term_fontrepl(p, TERMFONT_UNDER);
1.93 schwarze 462: continue;
1.83 schwarze 463: case ESCAPE_FONTBI:
1.70 schwarze 464: term_fontrepl(p, TERMFONT_BI);
1.93 schwarze 465: continue;
1.83 schwarze 466: case ESCAPE_FONT:
467: case ESCAPE_FONTROMAN:
1.20 schwarze 468: term_fontrepl(p, TERMFONT_NONE);
1.93 schwarze 469: continue;
1.83 schwarze 470: case ESCAPE_FONTPREV:
1.20 schwarze 471: term_fontlast(p);
1.93 schwarze 472: continue;
1.83 schwarze 473: case ESCAPE_NOSPACE:
1.108 schwarze 474: if (p->flags & TERMP_BACKAFTER)
475: p->flags &= ~TERMP_BACKAFTER;
476: else if (*word == '\0')
1.97 schwarze 477: p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE);
1.121 schwarze 478: continue;
479: case ESCAPE_HORIZ:
1.128 ! schwarze 480: if (a2roffsu(seq, &su, SCALE_EM) == NULL)
1.121 schwarze 481: continue;
482: uc = term_hspan(p, &su) / 24;
483: if (uc > 0)
484: while (uc-- > 0)
485: bufferc(p, ASCII_NBRSP);
486: else if (p->col > (size_t)(-uc))
487: p->col += uc;
488: else {
489: uc += p->col;
490: p->col = 0;
1.126 schwarze 491: if (p->tcol->offset > (size_t)(-uc)) {
1.121 schwarze 492: p->ti += uc;
1.126 schwarze 493: p->tcol->offset += uc;
1.121 schwarze 494: } else {
1.126 schwarze 495: p->ti -= p->tcol->offset;
496: p->tcol->offset = 0;
1.121 schwarze 497: }
1.122 schwarze 498: }
499: continue;
500: case ESCAPE_HLINE:
1.128 ! schwarze 501: if ((seq = a2roffsu(seq, &su, SCALE_EM)) == NULL)
1.122 schwarze 502: continue;
503: uc = term_hspan(p, &su) / 24;
504: if (uc <= 0) {
1.126 schwarze 505: if (p->tcol->rmargin <= p->tcol->offset)
1.122 schwarze 506: continue;
1.126 schwarze 507: lsz = p->tcol->rmargin - p->tcol->offset;
1.122 schwarze 508: } else
509: lsz = uc;
1.128 ! schwarze 510: if (*seq == '\0')
1.122 schwarze 511: uc = -1;
512: else if (*seq == '\\') {
513: seq++;
514: esc = mandoc_escape(&seq, &cp, &sz);
515: switch (esc) {
516: case ESCAPE_UNICODE:
517: uc = mchars_num2uc(cp + 1, sz - 1);
518: break;
519: case ESCAPE_NUMBERED:
520: uc = mchars_num2char(cp, sz);
521: break;
522: case ESCAPE_SPECIAL:
523: uc = mchars_spec2cp(cp, sz);
524: break;
525: default:
526: uc = -1;
527: break;
528: }
529: } else
530: uc = *seq;
531: if (uc < 0x20 || (uc > 0x7E && uc < 0xA0))
532: uc = '_';
533: if (p->enc == TERMENC_ASCII) {
534: cp = ascii_uc2str(uc);
535: csz = term_strlen(p, cp);
536: ssz = strlen(cp);
537: } else
538: csz = (*p->width)(p, uc);
539: while (lsz >= csz) {
540: if (p->enc == TERMENC_ASCII)
541: encode(p, cp, ssz);
542: else
543: encode1(p, uc);
544: lsz -= csz;
1.121 schwarze 545: }
1.93 schwarze 546: continue;
1.83 schwarze 547: case ESCAPE_SKIPCHAR:
1.108 schwarze 548: p->flags |= TERMP_BACKAFTER;
1.93 schwarze 549: continue;
1.103 schwarze 550: case ESCAPE_OVERSTRIKE:
551: cp = seq + sz;
552: while (seq < cp) {
553: if (*seq == '\\') {
554: mandoc_escape(&seq, NULL, NULL);
555: continue;
556: }
557: encode1(p, *seq++);
1.108 schwarze 558: if (seq < cp) {
559: if (p->flags & TERMP_BACKBEFORE)
560: p->flags |= TERMP_BACKAFTER;
561: else
562: p->flags |= TERMP_BACKBEFORE;
563: }
1.103 schwarze 564: }
1.109 schwarze 565: /* Trim trailing backspace/blank pair. */
1.125 schwarze 566: if (p->lastcol > 2 &&
1.126 schwarze 567: (p->tcol->buf[p->lastcol - 1] == ' ' ||
568: p->tcol->buf[p->lastcol - 1] == '\t'))
1.125 schwarze 569: p->lastcol -= 2;
570: if (p->col > p->lastcol)
571: p->col = p->lastcol;
1.108 schwarze 572: continue;
1.20 schwarze 573: default:
1.93 schwarze 574: continue;
575: }
576:
577: /*
578: * Common handling for Unicode and numbered
579: * character escape sequences.
580: */
581:
582: if (p->enc == TERMENC_ASCII) {
583: cp = ascii_uc2str(uc);
584: encode(p, cp, strlen(cp));
585: } else {
586: if ((uc < 0x20 && uc != 0x09) ||
587: (uc > 0x7E && uc < 0xA0))
588: uc = 0xFFFD;
589: encode1(p, uc);
1.20 schwarze 590: }
591: }
1.75 schwarze 592: p->flags &= ~TERMP_NBRWORD;
1.1 kristaps 593: }
594:
595: static void
1.126 schwarze 596: adjbuf(struct termp_col *c, size_t sz)
1.1 kristaps 597: {
1.126 schwarze 598: if (c->maxcols == 0)
599: c->maxcols = 1024;
600: while (c->maxcols <= sz)
601: c->maxcols <<= 2;
602: c->buf = mandoc_reallocarray(c->buf, c->maxcols, sizeof(*c->buf));
1.1 kristaps 603: }
604:
1.4 schwarze 605: static void
1.20 schwarze 606: bufferc(struct termp *p, char c)
607: {
1.124 schwarze 608: if (p->flags & TERMP_NOBUF) {
609: (*p->letter)(p, c);
610: return;
611: }
1.126 schwarze 612: if (p->col + 1 >= p->tcol->maxcols)
613: adjbuf(p->tcol, p->col + 1);
1.125 schwarze 614: if (p->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
1.126 schwarze 615: p->tcol->buf[p->col] = c;
1.125 schwarze 616: if (p->lastcol < ++p->col)
617: p->lastcol = p->col;
1.20 schwarze 618: }
619:
1.59 schwarze 620: /*
621: * See encode().
622: * Do this for a single (probably unicode) value.
623: * Does not check for non-decorated glyphs.
624: */
625: static void
626: encode1(struct termp *p, int c)
627: {
628: enum termfont f;
629:
1.124 schwarze 630: if (p->flags & TERMP_NOBUF) {
631: (*p->letter)(p, c);
632: return;
633: }
634:
1.126 schwarze 635: if (p->col + 7 >= p->tcol->maxcols)
636: adjbuf(p->tcol, p->col + 7);
1.59 schwarze 637:
1.115 schwarze 638: f = (c == ASCII_HYPH || c > 127 || isgraph(c)) ?
1.108 schwarze 639: p->fontq[p->fonti] : TERMFONT_NONE;
1.59 schwarze 640:
1.108 schwarze 641: if (p->flags & TERMP_BACKBEFORE) {
1.126 schwarze 642: if (p->tcol->buf[p->col - 1] == ' ' ||
643: p->tcol->buf[p->col - 1] == '\t')
1.109 schwarze 644: p->col--;
645: else
1.126 schwarze 646: p->tcol->buf[p->col++] = '\b';
1.108 schwarze 647: p->flags &= ~TERMP_BACKBEFORE;
648: }
1.126 schwarze 649: if (f == TERMFONT_UNDER || f == TERMFONT_BI) {
650: p->tcol->buf[p->col++] = '_';
651: p->tcol->buf[p->col++] = '\b';
652: }
653: if (f == TERMFONT_BOLD || f == TERMFONT_BI) {
654: if (c == ASCII_HYPH)
655: p->tcol->buf[p->col++] = '-';
1.70 schwarze 656: else
1.126 schwarze 657: p->tcol->buf[p->col++] = c;
658: p->tcol->buf[p->col++] = '\b';
1.70 schwarze 659: }
1.125 schwarze 660: if (p->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
1.126 schwarze 661: p->tcol->buf[p->col] = c;
1.125 schwarze 662: if (p->lastcol < ++p->col)
663: p->lastcol = p->col;
1.108 schwarze 664: if (p->flags & TERMP_BACKAFTER) {
665: p->flags |= TERMP_BACKBEFORE;
666: p->flags &= ~TERMP_BACKAFTER;
667: }
1.59 schwarze 668: }
1.20 schwarze 669:
670: static void
671: encode(struct termp *p, const char *word, size_t sz)
1.4 schwarze 672: {
1.71 schwarze 673: size_t i;
1.124 schwarze 674:
675: if (p->flags & TERMP_NOBUF) {
676: for (i = 0; i < sz; i++)
677: (*p->letter)(p, word[i]);
678: return;
679: }
1.59 schwarze 680:
1.126 schwarze 681: if (p->col + 2 + (sz * 5) >= p->tcol->maxcols)
682: adjbuf(p->tcol, p->col + 2 + (sz * 5));
1.46 schwarze 683:
1.71 schwarze 684: for (i = 0; i < sz; i++) {
1.70 schwarze 685: if (ASCII_HYPH == word[i] ||
686: isgraph((unsigned char)word[i]))
687: encode1(p, word[i]);
1.119 schwarze 688: else {
1.125 schwarze 689: if (p->lastcol <= p->col ||
690: (word[i] != ' ' && word[i] != ASCII_NBRSP))
1.126 schwarze 691: p->tcol->buf[p->col] = word[i];
1.125 schwarze 692: p->col++;
1.119 schwarze 693:
694: /*
695: * Postpone the effect of \z while handling
696: * an overstrike sequence from ascii_uc2str().
697: */
698:
699: if (word[i] == '\b' &&
700: (p->flags & TERMP_BACKBEFORE)) {
701: p->flags &= ~TERMP_BACKBEFORE;
702: p->flags |= TERMP_BACKAFTER;
703: }
704: }
1.4 schwarze 705: }
1.125 schwarze 706: if (p->lastcol < p->col)
707: p->lastcol = p->col;
1.80 schwarze 708: }
709:
710: void
711: term_setwidth(struct termp *p, const char *wstr)
712: {
713: struct roffsu su;
1.107 schwarze 714: int iop, width;
1.80 schwarze 715:
1.81 schwarze 716: iop = 0;
717: width = 0;
1.80 schwarze 718: if (NULL != wstr) {
719: switch (*wstr) {
1.83 schwarze 720: case '+':
1.80 schwarze 721: iop = 1;
722: wstr++;
723: break;
1.83 schwarze 724: case '-':
1.80 schwarze 725: iop = -1;
726: wstr++;
727: break;
728: default:
729: break;
730: }
1.128 ! schwarze 731: if (a2roffsu(wstr, &su, SCALE_MAX) != NULL)
1.81 schwarze 732: width = term_hspan(p, &su);
733: else
1.80 schwarze 734: iop = 0;
735: }
736: (*p->setwidth)(p, iop, width);
1.4 schwarze 737: }
1.16 schwarze 738:
739: size_t
1.39 schwarze 740: term_len(const struct termp *p, size_t sz)
741: {
742:
1.112 schwarze 743: return (*p->width)(p, ' ') * sz;
1.39 schwarze 744: }
745:
1.64 schwarze 746: static size_t
747: cond_width(const struct termp *p, int c, int *skip)
748: {
749:
750: if (*skip) {
751: (*skip) = 0;
1.112 schwarze 752: return 0;
1.64 schwarze 753: } else
1.112 schwarze 754: return (*p->width)(p, c);
1.64 schwarze 755: }
1.39 schwarze 756:
757: size_t
758: term_strlen(const struct termp *p, const char *cp)
759: {
1.59 schwarze 760: size_t sz, rsz, i;
1.93 schwarze 761: int ssz, skip, uc;
1.50 schwarze 762: const char *seq, *rhs;
1.59 schwarze 763: enum mandoc_esc esc;
1.77 schwarze 764: static const char rej[] = { '\\', ASCII_NBRSP, ASCII_HYPH,
765: ASCII_BREAK, '\0' };
1.59 schwarze 766:
767: /*
768: * Account for escaped sequences within string length
769: * calculations. This follows the logic in term_word() as we
770: * must calculate the width of produced strings.
771: */
772:
773: sz = 0;
1.64 schwarze 774: skip = 0;
1.59 schwarze 775: while ('\0' != *cp) {
776: rsz = strcspn(cp, rej);
777: for (i = 0; i < rsz; i++)
1.64 schwarze 778: sz += cond_width(p, *cp++, &skip);
1.59 schwarze 779:
780: switch (*cp) {
1.83 schwarze 781: case '\\':
1.59 schwarze 782: cp++;
783: esc = mandoc_escape(&cp, &seq, &ssz);
784: if (ESCAPE_ERROR == esc)
1.85 schwarze 785: continue;
1.59 schwarze 786:
787: rhs = NULL;
1.50 schwarze 788:
1.59 schwarze 789: switch (esc) {
1.83 schwarze 790: case ESCAPE_UNICODE:
1.94 schwarze 791: uc = mchars_num2uc(seq + 1, ssz - 1);
1.59 schwarze 792: break;
1.83 schwarze 793: case ESCAPE_NUMBERED:
1.93 schwarze 794: uc = mchars_num2char(seq, ssz);
795: if (uc < 0)
796: continue;
1.50 schwarze 797: break;
1.83 schwarze 798: case ESCAPE_SPECIAL:
1.93 schwarze 799: if (p->enc == TERMENC_ASCII) {
1.114 schwarze 800: rhs = mchars_spec2str(seq, ssz, &rsz);
1.93 schwarze 801: if (rhs != NULL)
802: break;
803: } else {
1.114 schwarze 804: uc = mchars_spec2cp(seq, ssz);
1.93 schwarze 805: if (uc > 0)
806: sz += cond_width(p, uc, &skip);
1.89 schwarze 807: }
1.93 schwarze 808: continue;
1.83 schwarze 809: case ESCAPE_SKIPCHAR:
1.64 schwarze 810: skip = 1;
1.103 schwarze 811: continue;
812: case ESCAPE_OVERSTRIKE:
813: rsz = 0;
814: rhs = seq + ssz;
815: while (seq < rhs) {
816: if (*seq == '\\') {
817: mandoc_escape(&seq, NULL, NULL);
818: continue;
819: }
820: i = (*p->width)(p, *seq++);
821: if (rsz < i)
822: rsz = i;
823: }
824: sz += rsz;
1.93 schwarze 825: continue;
1.50 schwarze 826: default:
1.93 schwarze 827: continue;
1.50 schwarze 828: }
1.39 schwarze 829:
1.93 schwarze 830: /*
831: * Common handling for Unicode and numbered
832: * character escape sequences.
833: */
834:
835: if (rhs == NULL) {
836: if (p->enc == TERMENC_ASCII) {
837: rhs = ascii_uc2str(uc);
838: rsz = strlen(rhs);
839: } else {
840: if ((uc < 0x20 && uc != 0x09) ||
841: (uc > 0x7E && uc < 0xA0))
842: uc = 0xFFFD;
843: sz += cond_width(p, uc, &skip);
844: continue;
845: }
846: }
1.59 schwarze 847:
1.64 schwarze 848: if (skip) {
849: skip = 0;
850: break;
851: }
1.93 schwarze 852:
853: /*
854: * Common handling for all escape sequences
855: * printing more than one character.
856: */
1.64 schwarze 857:
1.59 schwarze 858: for (i = 0; i < rsz; i++)
859: sz += (*p->width)(p, *rhs++);
860: break;
1.83 schwarze 861: case ASCII_NBRSP:
1.64 schwarze 862: sz += cond_width(p, ' ', &skip);
1.55 schwarze 863: cp++;
1.59 schwarze 864: break;
1.83 schwarze 865: case ASCII_HYPH:
1.64 schwarze 866: sz += cond_width(p, '-', &skip);
1.55 schwarze 867: cp++;
1.59 schwarze 868: break;
869: default:
870: break;
871: }
872: }
1.39 schwarze 873:
1.112 schwarze 874: return sz;
1.39 schwarze 875: }
876:
1.100 schwarze 877: int
1.39 schwarze 878: term_vspan(const struct termp *p, const struct roffsu *su)
1.16 schwarze 879: {
880: double r;
1.101 schwarze 881: int ri;
1.16 schwarze 882:
883: switch (su->unit) {
1.99 schwarze 884: case SCALE_BU:
885: r = su->scale / 40.0;
886: break;
1.83 schwarze 887: case SCALE_CM:
1.99 schwarze 888: r = su->scale * 6.0 / 2.54;
889: break;
890: case SCALE_FS:
891: r = su->scale * 65536.0 / 40.0;
1.16 schwarze 892: break;
1.83 schwarze 893: case SCALE_IN:
1.86 schwarze 894: r = su->scale * 6.0;
1.16 schwarze 895: break;
1.99 schwarze 896: case SCALE_MM:
897: r = su->scale * 0.006;
898: break;
1.83 schwarze 899: case SCALE_PC:
1.16 schwarze 900: r = su->scale;
901: break;
1.83 schwarze 902: case SCALE_PT:
1.99 schwarze 903: r = su->scale / 12.0;
1.16 schwarze 904: break;
1.99 schwarze 905: case SCALE_EN:
906: case SCALE_EM:
907: r = su->scale * 0.6;
1.16 schwarze 908: break;
1.83 schwarze 909: case SCALE_VS:
1.16 schwarze 910: r = su->scale;
911: break;
912: default:
1.99 schwarze 913: abort();
1.16 schwarze 914: }
1.101 schwarze 915: ri = r > 0.0 ? r + 0.4995 : r - 0.4995;
1.112 schwarze 916: return ri < 66 ? ri : 1;
1.16 schwarze 917: }
918:
1.107 schwarze 919: /*
920: * Convert a scaling width to basic units, rounding down.
921: */
1.100 schwarze 922: int
1.39 schwarze 923: term_hspan(const struct termp *p, const struct roffsu *su)
1.16 schwarze 924: {
925:
1.112 schwarze 926: return (*p->hspan)(p, su);
1.16 schwarze 927: }