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