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