Annotation of mandoc/term.c, Revision 1.94
1.94 ! kristaps 1: /* $Id: term.c,v 1.93 2009/07/24 11:54:25 kristaps Exp $ */
1.1 kristaps 2: /*
1.75 kristaps 3: * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
1.1 kristaps 4: *
5: * Permission to use, copy, modify, and distribute this software for any
1.74 kristaps 6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
1.1 kristaps 8: *
1.74 kristaps 9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1.1 kristaps 16: */
17: #include <assert.h>
1.23 kristaps 18: #include <err.h>
1.22 kristaps 19: #include <stdio.h>
1.1 kristaps 20: #include <stdlib.h>
21: #include <string.h>
22:
1.71 kristaps 23: #include "term.h"
24: #include "man.h"
25: #include "mdoc.h"
1.1 kristaps 26:
1.71 kristaps 27: extern int man_run(struct termp *,
28: const struct man *);
29: extern int mdoc_run(struct termp *,
30: const struct mdoc *);
1.1 kristaps 31:
1.71 kristaps 32: static struct termp *term_alloc(enum termenc);
33: static void term_free(struct termp *);
1.86 kristaps 34: static void term_pescape(struct termp *, const char **);
1.71 kristaps 35: static void term_nescape(struct termp *,
36: const char *, size_t);
1.94 ! kristaps 37: static void term_sescape(struct termp *,
! 38: const char *, size_t);
1.71 kristaps 39: static void term_chara(struct termp *, char);
1.79 kristaps 40: static void term_encodea(struct termp *, char);
1.86 kristaps 41: static int term_isopendelim(const char *);
42: static int term_isclosedelim(const char *);
1.1 kristaps 43:
44:
1.71 kristaps 45: void *
46: ascii_alloc(void)
1.10 kristaps 47: {
1.1 kristaps 48:
1.71 kristaps 49: return(term_alloc(TERMENC_ASCII));
1.1 kristaps 50: }
51:
52:
1.71 kristaps 53: int
1.72 kristaps 54: terminal_man(void *arg, const struct man *man)
1.1 kristaps 55: {
1.71 kristaps 56: struct termp *p;
1.1 kristaps 57:
1.71 kristaps 58: p = (struct termp *)arg;
59: if (NULL == p->symtab)
60: p->symtab = term_ascii2htab();
1.2 kristaps 61:
1.72 kristaps 62: return(man_run(p, man));
63: }
64:
65:
66: int
67: terminal_mdoc(void *arg, const struct mdoc *mdoc)
68: {
69: struct termp *p;
70:
71: p = (struct termp *)arg;
72: if (NULL == p->symtab)
73: p->symtab = term_ascii2htab();
1.2 kristaps 74:
1.72 kristaps 75: return(mdoc_run(p, mdoc));
1.1 kristaps 76: }
77:
78:
1.71 kristaps 79: void
80: terminal_free(void *arg)
1.11 kristaps 81: {
82:
1.71 kristaps 83: term_free((struct termp *)arg);
1.11 kristaps 84: }
85:
86:
1.71 kristaps 87: static void
88: term_free(struct termp *p)
1.14 kristaps 89: {
90:
1.71 kristaps 91: if (p->buf)
92: free(p->buf);
93: if (TERMENC_ASCII == p->enc && p->symtab)
94: term_asciifree(p->symtab);
1.14 kristaps 95:
1.71 kristaps 96: free(p);
1.14 kristaps 97: }
98:
99:
1.71 kristaps 100: static struct termp *
101: term_alloc(enum termenc enc)
1.14 kristaps 102: {
1.71 kristaps 103: struct termp *p;
1.14 kristaps 104:
1.71 kristaps 105: if (NULL == (p = malloc(sizeof(struct termp))))
106: err(1, "malloc");
107: bzero(p, sizeof(struct termp));
1.80 kristaps 108: p->maxrmargin = 78;
1.71 kristaps 109: p->enc = enc;
110: return(p);
1.14 kristaps 111: }
112:
113:
114: static int
1.86 kristaps 115: term_isclosedelim(const char *p)
1.14 kristaps 116: {
117:
1.86 kristaps 118: if ( ! (*p && 0 == *(p + 1)))
1.71 kristaps 119: return(0);
1.14 kristaps 120:
1.71 kristaps 121: switch (*p) {
122: case('.'):
123: /* FALLTHROUGH */
124: case(','):
125: /* FALLTHROUGH */
126: case(';'):
127: /* FALLTHROUGH */
128: case(':'):
129: /* FALLTHROUGH */
130: case('?'):
131: /* FALLTHROUGH */
132: case('!'):
133: /* FALLTHROUGH */
134: case(')'):
135: /* FALLTHROUGH */
136: case(']'):
137: /* FALLTHROUGH */
138: case('}'):
139: return(1);
140: default:
141: break;
142: }
1.14 kristaps 143:
1.71 kristaps 144: return(0);
1.30 kristaps 145: }
146:
147:
1.14 kristaps 148: static int
1.86 kristaps 149: term_isopendelim(const char *p)
1.14 kristaps 150: {
1.43 kristaps 151:
1.86 kristaps 152: if ( ! (*p && 0 == *(p + 1)))
1.71 kristaps 153: return(0);
1.14 kristaps 154:
1.71 kristaps 155: switch (*p) {
156: case('('):
157: /* FALLTHROUGH */
158: case('['):
159: /* FALLTHROUGH */
160: case('{'):
161: return(1);
162: default:
163: break;
164: }
1.43 kristaps 165:
1.14 kristaps 166: return(0);
167: }
1.15 kristaps 168:
169:
1.71 kristaps 170: /*
171: * Flush a line of text. A "line" is loosely defined as being something
172: * that should be followed by a newline, regardless of whether it's
173: * broken apart by newlines getting there. A line can also be a
174: * fragment of a columnar list.
175: *
176: * Specifically, a line is whatever's in p->buf of length p->col, which
177: * is zeroed after this function returns.
178: *
1.84 kristaps 179: * The usage of termp:flags is as follows:
1.71 kristaps 180: *
181: * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
182: * offset value. This is useful when doing columnar lists where the
183: * prior column has right-padded.
184: *
185: * - TERMP_NOBREAK: this is the most important and is used when making
186: * columns. In short: don't print a newline and instead pad to the
187: * right margin. Used in conjunction with TERMP_NOLPAD.
188: *
1.91 kristaps 189: * - TERMP_TWOSPACE: when padding, make sure there are at least two
190: * space characters of padding. Otherwise, rather break the line.
191: *
1.84 kristaps 192: * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and
193: * the line is overrun, and don't pad-right if it's underrun.
194: *
195: * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when
196: * overruning, instead save the position and continue at that point
197: * when the next invocation.
1.71 kristaps 198: *
199: * In-line line breaking:
200: *
201: * If TERMP_NOBREAK is specified and the line overruns the right
202: * margin, it will break and pad-right to the right margin after
203: * writing. If maxrmargin is violated, it will break and continue
204: * writing from the right-margin, which will lead to the above
205: * scenario upon exit.
206: *
207: * Otherwise, the line will break at the right margin. Extremely long
208: * lines will cause the system to emit a warning (TODO: hyphenate, if
209: * possible).
1.82 kristaps 210: *
211: * FIXME: newline breaks occur (in groff) also occur when a single
1.90 kristaps 212: * space follows a NOBREAK (try `Bl -tag')
213: *
214: * FIXME: there's a newline error where a `Bl -diag' will have a
215: * trailing newline if the line is exactly 73 chars long.
1.71 kristaps 216: */
217: void
218: term_flushln(struct termp *p)
1.53 kristaps 219: {
1.71 kristaps 220: int i, j;
1.81 kristaps 221: size_t vbl, vsz, vis, maxvis, mmax, bp;
1.91 kristaps 222: static int overstep = 0;
1.53 kristaps 223:
1.71 kristaps 224: /*
225: * First, establish the maximum columns of "visible" content.
226: * This is usually the difference between the right-margin and
227: * an indentation, but can be, for tagged lists or columns, a
228: * small set of values.
229: */
1.53 kristaps 230:
1.71 kristaps 231: assert(p->offset < p->rmargin);
1.92 kristaps 232: assert((int)(p->rmargin - p->offset) - overstep > 0);
233:
234: maxvis = /* LINTED */
235: p->rmargin - p->offset - overstep;
236: mmax = /* LINTED */
237: p->maxrmargin - p->offset - overstep;
238:
1.71 kristaps 239: bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
240: vis = 0;
1.91 kristaps 241: overstep = 0;
1.84 kristaps 242:
1.71 kristaps 243: /*
244: * If in the standard case (left-justified), then begin with our
245: * indentation, otherwise (columns, etc.) just start spitting
246: * out text.
247: */
1.53 kristaps 248:
1.71 kristaps 249: if ( ! (p->flags & TERMP_NOLPAD))
250: /* LINTED */
251: for (j = 0; j < (int)p->offset; j++)
252: putchar(' ');
253:
254: for (i = 0; i < (int)p->col; i++) {
255: /*
256: * Count up visible word characters. Control sequences
257: * (starting with the CSI) aren't counted. A space
258: * generates a non-printing word, which is valid (the
259: * space is printed according to regular spacing rules).
260: */
261:
262: /* LINTED */
263: for (j = i, vsz = 0; j < (int)p->col; j++) {
1.93 kristaps 264: if (j && ' ' == p->buf[j])
1.71 kristaps 265: break;
266: else if (8 == p->buf[j])
1.89 kristaps 267: vsz--;
1.71 kristaps 268: else
269: vsz++;
270: }
1.53 kristaps 271:
1.71 kristaps 272: /*
1.81 kristaps 273: * Choose the number of blanks to prepend: no blank at the
274: * beginning of a line, one between words -- but do not
275: * actually write them yet.
1.71 kristaps 276: */
1.81 kristaps 277: vbl = (size_t)(0 == vis ? 0 : 1);
1.71 kristaps 278:
1.81 kristaps 279: /*
280: * Find out whether we would exceed the right margin.
281: * If so, break to the next line. (TODO: hyphenate)
282: * Otherwise, write the chosen number of blanks now.
283: */
284: if (vis && vis + vbl + vsz > bp) {
285: putchar('\n');
286: if (TERMP_NOBREAK & p->flags) {
287: for (j = 0; j < (int)p->rmargin; j++)
288: putchar(' ');
289: vis = p->rmargin - p->offset;
290: } else {
1.71 kristaps 291: for (j = 0; j < (int)p->offset; j++)
292: putchar(' ');
293: vis = 0;
1.81 kristaps 294: }
295: } else {
296: for (j = 0; j < (int)vbl; j++)
1.71 kristaps 297: putchar(' ');
1.81 kristaps 298: vis += vbl;
1.71 kristaps 299: }
1.53 kristaps 300:
1.78 kristaps 301: /*
1.81 kristaps 302: * Finally, write out the word.
1.71 kristaps 303: */
304: for ( ; i < (int)p->col; i++) {
305: if (' ' == p->buf[i])
306: break;
307: putchar(p->buf[i]);
308: }
309: vis += vsz;
310: }
1.91 kristaps 311: p->col = 0;
1.15 kristaps 312:
1.91 kristaps 313: if ( ! (TERMP_NOBREAK & p->flags)) {
314: putchar('\n');
1.15 kristaps 315: return;
1.71 kristaps 316: }
1.15 kristaps 317:
1.91 kristaps 318: if (TERMP_HANG & p->flags) {
319: /* We need one blank after the tag. */
1.92 kristaps 320: overstep = /* LINTED */
321: vis - maxvis + 1;
1.91 kristaps 322:
323: /*
324: * Behave exactly the same way as groff:
1.92 kristaps 325: * If we have overstepped the margin, temporarily move
326: * it to the right and flag the rest of the line to be
327: * shorter.
1.91 kristaps 328: * If we landed right at the margin, be happy.
1.92 kristaps 329: * If we are one step before the margin, temporarily
330: * move it one step LEFT and flag the rest of the line
331: * to be longer.
1.91 kristaps 332: */
1.92 kristaps 333: if (overstep >= -1) {
334: assert((int)maxvis + overstep >= 0);
335: /* LINTED */
1.91 kristaps 336: maxvis += overstep;
1.92 kristaps 337: } else
1.91 kristaps 338: overstep = 0;
339:
340: } else if (TERMP_DANGLE & p->flags)
341: return;
1.15 kristaps 342:
1.92 kristaps 343: /* Right-pad. */
344: if (maxvis > vis + /* LINTED */
345: ((TERMP_TWOSPACE & p->flags) ? 1 : 0))
1.91 kristaps 346: for ( ; vis < maxvis; vis++)
347: putchar(' ');
1.92 kristaps 348: else { /* ...or newline break. */
1.71 kristaps 349: putchar('\n');
1.91 kristaps 350: for (i = 0; i < (int)p->rmargin; i++)
351: putchar(' ');
352: }
1.15 kristaps 353: }
354:
355:
1.71 kristaps 356: /*
357: * A newline only breaks an existing line; it won't assert vertical
358: * space. All data in the output buffer is flushed prior to the newline
359: * assertion.
360: */
361: void
362: term_newln(struct termp *p)
1.15 kristaps 363: {
364:
1.71 kristaps 365: p->flags |= TERMP_NOSPACE;
366: if (0 == p->col) {
367: p->flags &= ~TERMP_NOLPAD;
1.15 kristaps 368: return;
1.16 kristaps 369: }
1.71 kristaps 370: term_flushln(p);
371: p->flags &= ~TERMP_NOLPAD;
1.16 kristaps 372: }
373:
374:
1.71 kristaps 375: /*
376: * Asserts a vertical space (a full, empty line-break between lines).
377: * Note that if used twice, this will cause two blank spaces and so on.
378: * All data in the output buffer is flushed prior to the newline
379: * assertion.
380: */
381: void
382: term_vspace(struct termp *p)
1.16 kristaps 383: {
384:
1.62 kristaps 385: term_newln(p);
1.71 kristaps 386: putchar('\n');
1.16 kristaps 387: }
388:
389:
1.71 kristaps 390: /*
391: * Determine the symbol indicated by an escape sequences, that is, one
392: * starting with a backslash. Once done, we pass this value into the
393: * output buffer by way of the symbol table.
394: */
395: static void
396: term_nescape(struct termp *p, const char *word, size_t len)
1.17 kristaps 397: {
1.71 kristaps 398: const char *rhs;
399: size_t sz;
1.79 kristaps 400: int i;
1.17 kristaps 401:
1.83 kristaps 402: rhs = term_a2ascii(p->symtab, word, len, &sz);
1.86 kristaps 403:
1.94 ! kristaps 404: if (NULL == rhs)
! 405: return;
! 406: for (i = 0; i < (int)sz; i++)
! 407: term_encodea(p, rhs[i]);
! 408: }
! 409:
! 410:
! 411: static void
! 412: term_sescape(struct termp *p, const char *word, size_t len)
! 413: {
! 414: const char *rhs;
! 415: size_t sz;
! 416: int i;
! 417:
! 418: rhs = term_a2res(p->symtab, word, len, &sz);
! 419:
! 420: if (NULL == rhs)
! 421: return;
! 422: for (i = 0; i < (int)sz; i++)
! 423: term_encodea(p, rhs[i]);
1.17 kristaps 424: }
425:
426:
1.71 kristaps 427: /*
428: * Handle an escape sequence: determine its length and pass it to the
429: * escape-symbol look table. Note that we assume mdoc(3) has validated
430: * the escape sequence (we assert upon badly-formed escape sequences).
431: */
432: static void
1.86 kristaps 433: term_pescape(struct termp *p, const char **word)
1.17 kristaps 434: {
1.71 kristaps 435: int j;
1.86 kristaps 436: const char *wp;
437:
438: wp = *word;
1.17 kristaps 439:
1.86 kristaps 440: if (0 == *(++wp)) {
441: *word = wp;
1.71 kristaps 442: return;
1.86 kristaps 443: }
1.17 kristaps 444:
1.86 kristaps 445: if ('(' == *wp) {
446: wp++;
447: if (0 == *wp || 0 == *(wp + 1)) {
448: *word = 0 == *wp ? wp : wp + 1;
1.71 kristaps 449: return;
1.86 kristaps 450: }
1.22 kristaps 451:
1.86 kristaps 452: term_nescape(p, wp, 2);
453: *word = ++wp;
1.71 kristaps 454: return;
1.22 kristaps 455:
1.86 kristaps 456: } else if ('*' == *wp) {
457: if (0 == *(++wp)) {
458: *word = wp;
1.71 kristaps 459: return;
1.86 kristaps 460: }
1.22 kristaps 461:
1.86 kristaps 462: switch (*wp) {
1.71 kristaps 463: case ('('):
1.86 kristaps 464: wp++;
465: if (0 == *wp || 0 == *(wp + 1)) {
466: *word = 0 == *wp ? wp : wp + 1;
1.71 kristaps 467: return;
1.86 kristaps 468: }
1.65 kristaps 469:
1.94 ! kristaps 470: term_sescape(p, wp, 2);
1.86 kristaps 471: *word = ++wp;
1.71 kristaps 472: return;
473: case ('['):
474: break;
475: default:
1.94 ! kristaps 476: term_sescape(p, wp, 1);
1.86 kristaps 477: *word = wp;
1.71 kristaps 478: return;
479: }
480:
1.86 kristaps 481: } else if ('f' == *wp) {
482: if (0 == *(++wp)) {
483: *word = wp;
1.71 kristaps 484: return;
1.86 kristaps 485: }
486:
487: switch (*wp) {
1.71 kristaps 488: case ('B'):
489: p->flags |= TERMP_BOLD;
490: break;
491: case ('I'):
492: p->flags |= TERMP_UNDER;
493: break;
494: case ('P'):
495: /* FALLTHROUGH */
496: case ('R'):
497: p->flags &= ~TERMP_STYLE;
498: break;
499: default:
500: break;
501: }
1.86 kristaps 502:
503: *word = wp;
1.71 kristaps 504: return;
1.22 kristaps 505:
1.86 kristaps 506: } else if ('[' != *wp) {
507: term_nescape(p, wp, 1);
508: *word = wp;
1.71 kristaps 509: return;
510: }
1.28 kristaps 511:
1.86 kristaps 512: wp++;
513: for (j = 0; *wp && ']' != *wp; wp++, j++)
1.71 kristaps 514: /* Loop... */ ;
1.28 kristaps 515:
1.86 kristaps 516: if (0 == *wp) {
517: *word = wp;
1.71 kristaps 518: return;
1.86 kristaps 519: }
1.48 kristaps 520:
1.86 kristaps 521: term_nescape(p, wp - j, (size_t)j);
522: *word = wp;
1.48 kristaps 523: }
524:
525:
1.71 kristaps 526: /*
527: * Handle pwords, partial words, which may be either a single word or a
528: * phrase that cannot be broken down (such as a literal string). This
529: * handles word styling.
530: */
1.86 kristaps 531: void
532: term_word(struct termp *p, const char *word)
1.65 kristaps 533: {
1.88 kristaps 534: const char *sv;
1.71 kristaps 535:
1.86 kristaps 536: if (term_isclosedelim(word))
1.71 kristaps 537: if ( ! (TERMP_IGNDELIM & p->flags))
538: p->flags |= TERMP_NOSPACE;
1.65 kristaps 539:
1.71 kristaps 540: if ( ! (TERMP_NOSPACE & p->flags))
541: term_chara(p, ' ');
1.65 kristaps 542:
1.71 kristaps 543: if ( ! (p->flags & TERMP_NONOSPACE))
544: p->flags &= ~TERMP_NOSPACE;
1.65 kristaps 545:
1.71 kristaps 546: /*
547: * If ANSI (word-length styling), then apply our style now,
548: * before the word.
549: */
1.28 kristaps 550:
1.88 kristaps 551: for (sv = word; *word; word++)
1.86 kristaps 552: if ('\\' != *word)
553: term_encodea(p, *word);
1.79 kristaps 554: else
1.86 kristaps 555: term_pescape(p, &word);
1.65 kristaps 556:
1.88 kristaps 557: if (term_isopendelim(sv))
1.71 kristaps 558: p->flags |= TERMP_NOSPACE;
1.65 kristaps 559: }
560:
561:
1.71 kristaps 562: /*
563: * Insert a single character into the line-buffer. If the buffer's
564: * space is exceeded, then allocate more space by doubling the buffer
565: * size.
566: */
567: static void
568: term_chara(struct termp *p, char c)
1.51 kristaps 569: {
1.71 kristaps 570: size_t s;
1.51 kristaps 571:
1.71 kristaps 572: if (p->col + 1 >= p->maxcols) {
573: if (0 == p->maxcols)
574: p->maxcols = 256;
575: s = p->maxcols * 2;
576: p->buf = realloc(p->buf, s);
577: if (NULL == p->buf)
578: err(1, "realloc");
579: p->maxcols = s;
580: }
581: p->buf[(int)(p->col)++] = c;
1.51 kristaps 582: }
583:
1.79 kristaps 584:
585: static void
586: term_encodea(struct termp *p, char c)
587: {
1.89 kristaps 588:
589: if (' ' != c && TERMP_STYLE & p->flags) {
1.79 kristaps 590: if (TERMP_BOLD & p->flags) {
591: term_chara(p, c);
592: term_chara(p, 8);
593: }
594: if (TERMP_UNDER & p->flags) {
595: term_chara(p, '_');
596: term_chara(p, 8);
597: }
598: }
599: term_chara(p, c);
600: }
CVSweb