Annotation of src/usr.bin/mandoc/term.c, Revision 1.14
1.14 ! schwarze 1: /* $Id: term.c,v 1.13 2009/09/21 20:57:57 schwarze Exp $ */
1.1 kristaps 2: /*
1.2 schwarze 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.2 schwarze 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.2 schwarze 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>
18: #include <err.h>
19: #include <stdio.h>
20: #include <stdlib.h>
21: #include <string.h>
22:
23: #include "term.h"
24: #include "man.h"
25: #include "mdoc.h"
26:
1.13 schwarze 27: extern void man_run(struct termp *,
1.1 kristaps 28: const struct man *);
1.13 schwarze 29: extern void mdoc_run(struct termp *,
1.1 kristaps 30: const struct mdoc *);
31:
32: static struct termp *term_alloc(enum termenc);
33: static void term_free(struct termp *);
1.11 schwarze 34:
35: static void do_escaped(struct termp *, const char **);
36: static void do_special(struct termp *,
37: const char *, size_t);
38: static void do_reserved(struct termp *,
1.1 kristaps 39: const char *, size_t);
1.11 schwarze 40: static void buffer(struct termp *, char);
41: static void encode(struct termp *, char);
1.1 kristaps 42:
43:
44: void *
45: ascii_alloc(void)
46: {
47:
48: return(term_alloc(TERMENC_ASCII));
49: }
50:
51:
1.13 schwarze 52: void
1.1 kristaps 53: terminal_man(void *arg, const struct man *man)
54: {
55: struct termp *p;
56:
57: p = (struct termp *)arg;
58: if (NULL == p->symtab)
59: p->symtab = term_ascii2htab();
60:
1.13 schwarze 61: man_run(p, man);
1.1 kristaps 62: }
63:
64:
1.13 schwarze 65: void
1.1 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();
73:
1.13 schwarze 74: mdoc_run(p, mdoc);
1.1 kristaps 75: }
76:
77:
78: void
79: terminal_free(void *arg)
80: {
81:
82: term_free((struct termp *)arg);
83: }
84:
85:
86: static void
87: term_free(struct termp *p)
88: {
89:
90: if (p->buf)
91: free(p->buf);
92: if (TERMENC_ASCII == p->enc && p->symtab)
93: term_asciifree(p->symtab);
94:
95: free(p);
96: }
97:
98:
99: static struct termp *
100: term_alloc(enum termenc enc)
101: {
102: struct termp *p;
103:
104: if (NULL == (p = malloc(sizeof(struct termp))))
105: err(1, "malloc");
106: bzero(p, sizeof(struct termp));
107: p->maxrmargin = 78;
108: p->enc = enc;
109: return(p);
110: }
111:
112:
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.6 schwarze 122: * The usage of termp:flags is as follows:
1.1 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.9 schwarze 132: * - TERMP_TWOSPACE: when padding, make sure there are at least two
133: * space characters of padding. Otherwise, rather break the line.
134: *
1.6 schwarze 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.1 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)
156: {
157: int i, j;
1.5 schwarze 158: size_t vbl, vsz, vis, maxvis, mmax, bp;
1.9 schwarze 159: static int overstep = 0;
1.1 kristaps 160:
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: */
167:
168: assert(p->offset < p->rmargin);
1.9 schwarze 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.1 kristaps 176: bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
177: vis = 0;
1.9 schwarze 178: overstep = 0;
1.6 schwarze 179:
1.1 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: */
185:
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.10 schwarze 201: if (j && ' ' == p->buf[j])
1.1 kristaps 202: break;
203: else if (8 == p->buf[j])
1.8 schwarze 204: vsz--;
1.1 kristaps 205: else
206: vsz++;
207: }
208:
209: /*
1.5 schwarze 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.1 kristaps 213: */
1.5 schwarze 214: vbl = (size_t)(0 == vis ? 0 : 1);
1.1 kristaps 215:
1.5 schwarze 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.1 kristaps 228: for (j = 0; j < (int)p->offset; j++)
229: putchar(' ');
230: vis = 0;
1.5 schwarze 231: }
232: } else {
233: for (j = 0; j < (int)vbl; j++)
1.1 kristaps 234: putchar(' ');
1.5 schwarze 235: vis += vbl;
1.1 kristaps 236: }
237:
1.3 schwarze 238: /*
1.5 schwarze 239: * Finally, write out the word.
1.1 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.9 schwarze 248: p->col = 0;
1.1 kristaps 249:
1.9 schwarze 250: if ( ! (TERMP_NOBREAK & p->flags)) {
251: putchar('\n');
1.1 kristaps 252: return;
253: }
254:
1.9 schwarze 255: if (TERMP_HANG & p->flags) {
256: /* We need one blank after the tag. */
257: overstep = /* LINTED */
258: vis - maxvis + 1;
259:
260: /*
261: * Behave exactly the same way as groff:
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.
265: * If we landed right at the margin, be happy.
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.
269: */
270: if (overstep >= -1) {
271: assert((int)maxvis + overstep >= 0);
272: /* LINTED */
273: maxvis += overstep;
274: } else
275: overstep = 0;
276:
277: } else if (TERMP_DANGLE & p->flags)
278: return;
1.1 kristaps 279:
1.9 schwarze 280: /* Right-pad. */
281: if (maxvis > vis + /* LINTED */
282: ((TERMP_TWOSPACE & p->flags) ? 1 : 0))
283: for ( ; vis < maxvis; vis++)
284: putchar(' ');
285: else { /* ...or newline break. */
1.1 kristaps 286: putchar('\n');
1.9 schwarze 287: for (i = 0; i < (int)p->rmargin; i++)
288: putchar(' ');
289: }
1.1 kristaps 290: }
291:
292:
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)
300: {
301:
302: p->flags |= TERMP_NOSPACE;
303: if (0 == p->col) {
304: p->flags &= ~TERMP_NOLPAD;
305: return;
306: }
307: term_flushln(p);
308: p->flags &= ~TERMP_NOLPAD;
309: }
310:
311:
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)
320: {
321:
322: term_newln(p);
323: putchar('\n');
324: }
325:
326:
327: static void
1.11 schwarze 328: do_special(struct termp *p, const char *word, size_t len)
1.1 kristaps 329: {
330: const char *rhs;
331: size_t sz;
1.4 schwarze 332: int i;
1.1 kristaps 333:
1.5 schwarze 334: rhs = term_a2ascii(p->symtab, word, len, &sz);
1.7 schwarze 335:
1.11 schwarze 336: if (NULL == rhs) {
337: #if 0
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
343: return;
344: }
345: for (i = 0; i < (int)sz; i++)
346: encode(p, rhs[i]);
347: }
348:
349:
350: static void
351: do_reserved(struct termp *p, const char *word, size_t len)
352: {
353: const char *rhs;
354: size_t sz;
355: int i;
356:
357: rhs = term_a2res(p->symtab, word, len, &sz);
358:
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
366: return;
367: }
368: for (i = 0; i < (int)sz; i++)
369: encode(p, rhs[i]);
1.1 kristaps 370: }
371:
372:
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.11 schwarze 379: do_escaped(struct termp *p, const char **word)
1.1 kristaps 380: {
1.11 schwarze 381: int j, type;
1.7 schwarze 382: const char *wp;
383:
384: wp = *word;
1.11 schwarze 385: type = 1;
1.1 kristaps 386:
1.7 schwarze 387: if (0 == *(++wp)) {
388: *word = wp;
1.1 kristaps 389: return;
1.7 schwarze 390: }
1.1 kristaps 391:
1.7 schwarze 392: if ('(' == *wp) {
393: wp++;
394: if (0 == *wp || 0 == *(wp + 1)) {
395: *word = 0 == *wp ? wp : wp + 1;
1.1 kristaps 396: return;
1.7 schwarze 397: }
1.1 kristaps 398:
1.11 schwarze 399: do_special(p, wp, 2);
1.7 schwarze 400: *word = ++wp;
1.1 kristaps 401: return;
402:
1.7 schwarze 403: } else if ('*' == *wp) {
404: if (0 == *(++wp)) {
405: *word = wp;
1.1 kristaps 406: return;
1.7 schwarze 407: }
1.1 kristaps 408:
1.7 schwarze 409: switch (*wp) {
1.1 kristaps 410: case ('('):
1.7 schwarze 411: wp++;
412: if (0 == *wp || 0 == *(wp + 1)) {
413: *word = 0 == *wp ? wp : wp + 1;
1.1 kristaps 414: return;
1.7 schwarze 415: }
1.1 kristaps 416:
1.11 schwarze 417: do_reserved(p, wp, 2);
1.7 schwarze 418: *word = ++wp;
1.1 kristaps 419: return;
420: case ('['):
1.11 schwarze 421: type = 0;
1.1 kristaps 422: break;
423: default:
1.11 schwarze 424: do_reserved(p, wp, 1);
1.7 schwarze 425: *word = wp;
1.1 kristaps 426: return;
427: }
428:
1.7 schwarze 429: } else if ('f' == *wp) {
430: if (0 == *(++wp)) {
431: *word = wp;
1.1 kristaps 432: return;
1.7 schwarze 433: }
434:
435: switch (*wp) {
1.1 kristaps 436: case ('B'):
1.12 schwarze 437: p->bold++;
1.1 kristaps 438: break;
439: case ('I'):
1.12 schwarze 440: p->under++;
1.1 kristaps 441: break;
442: case ('P'):
443: /* FALLTHROUGH */
444: case ('R'):
1.12 schwarze 445: p->bold = p->under = 0;
1.1 kristaps 446: break;
447: default:
448: break;
449: }
1.7 schwarze 450:
451: *word = wp;
1.1 kristaps 452: return;
453:
1.7 schwarze 454: } else if ('[' != *wp) {
1.11 schwarze 455: do_special(p, wp, 1);
1.7 schwarze 456: *word = wp;
1.1 kristaps 457: return;
458: }
459:
1.7 schwarze 460: wp++;
461: for (j = 0; *wp && ']' != *wp; wp++, j++)
1.1 kristaps 462: /* Loop... */ ;
463:
1.7 schwarze 464: if (0 == *wp) {
465: *word = wp;
1.1 kristaps 466: return;
1.7 schwarze 467: }
1.1 kristaps 468:
1.11 schwarze 469: if (type)
470: do_special(p, wp - j, (size_t)j);
471: else
472: do_reserved(p, wp - j, (size_t)j);
1.7 schwarze 473: *word = wp;
1.1 kristaps 474: }
475:
476:
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.7 schwarze 482: void
483: term_word(struct termp *p, const char *word)
1.1 kristaps 484: {
1.7 schwarze 485: const char *sv;
1.1 kristaps 486:
1.14 ! schwarze 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.1 kristaps 514:
515: if ( ! (TERMP_NOSPACE & p->flags))
1.11 schwarze 516: buffer(p, ' ');
1.1 kristaps 517:
518: if ( ! (p->flags & TERMP_NONOSPACE))
519: p->flags &= ~TERMP_NOSPACE;
520:
1.14 ! schwarze 521: for ( ; *word; word++)
1.7 schwarze 522: if ('\\' != *word)
1.11 schwarze 523: encode(p, *word);
1.4 schwarze 524: else
1.11 schwarze 525: do_escaped(p, &word);
1.1 kristaps 526:
1.14 ! schwarze 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.1 kristaps 539: }
540:
541:
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.11 schwarze 548: buffer(struct termp *p, char c)
1.1 kristaps 549: {
550: size_t s;
551:
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)
558: err(1, "realloc");
559: p->maxcols = s;
560: }
561: p->buf[(int)(p->col)++] = c;
562: }
563:
1.4 schwarze 564:
565: static void
1.11 schwarze 566: encode(struct termp *p, char c)
1.4 schwarze 567: {
1.7 schwarze 568:
1.12 schwarze 569: if (' ' != c) {
570: if (p->bold) {
1.11 schwarze 571: buffer(p, c);
572: buffer(p, 8);
1.4 schwarze 573: }
1.12 schwarze 574: if (p->under) {
1.11 schwarze 575: buffer(p, '_');
576: buffer(p, 8);
1.4 schwarze 577: }
578: }
1.11 schwarze 579: buffer(p, c);
1.4 schwarze 580: }