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