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