Annotation of mandoc/term.c, Revision 1.128
1.128 ! kristaps 1: /* $Id: term.c,v 1.127 2009/11/12 08:21:06 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: */
1.128 ! kristaps 17: #ifdef HAVE_CONFIG_H
! 18: #include "config.h"
! 19: #endif
! 20:
1.126 kristaps 21: #include <sys/types.h>
22:
1.1 kristaps 23: #include <assert.h>
1.122 kristaps 24: #include <ctype.h>
1.22 kristaps 25: #include <stdio.h>
1.1 kristaps 26: #include <stdlib.h>
27: #include <string.h>
1.113 kristaps 28: #include <time.h>
1.1 kristaps 29:
1.101 kristaps 30: #include "chars.h"
1.107 kristaps 31: #include "out.h"
1.71 kristaps 32: #include "term.h"
33: #include "man.h"
34: #include "mdoc.h"
1.105 kristaps 35: #include "main.h"
1.1 kristaps 36:
1.71 kristaps 37: static struct termp *term_alloc(enum termenc);
38: static void term_free(struct termp *);
1.125 kristaps 39: static void spec(struct termp *, const char *, size_t);
40: static void res(struct termp *, const char *, size_t);
41: static void buffera(struct termp *, const char *, size_t);
42: static void bufferc(struct termp *, char);
43: static void adjbuf(struct termp *p, size_t);
44: static void encode(struct termp *, const char *, size_t);
1.1 kristaps 45:
46:
1.71 kristaps 47: void *
48: ascii_alloc(void)
1.10 kristaps 49: {
1.1 kristaps 50:
1.71 kristaps 51: return(term_alloc(TERMENC_ASCII));
1.1 kristaps 52: }
53:
54:
1.99 kristaps 55: void
1.71 kristaps 56: terminal_free(void *arg)
1.11 kristaps 57: {
58:
1.71 kristaps 59: term_free((struct termp *)arg);
1.11 kristaps 60: }
61:
62:
1.71 kristaps 63: static void
64: term_free(struct termp *p)
1.14 kristaps 65: {
66:
1.71 kristaps 67: if (p->buf)
68: free(p->buf);
1.102 kristaps 69: if (p->symtab)
1.101 kristaps 70: chars_free(p->symtab);
1.14 kristaps 71:
1.71 kristaps 72: free(p);
1.14 kristaps 73: }
74:
75:
1.71 kristaps 76: static struct termp *
77: term_alloc(enum termenc enc)
1.14 kristaps 78: {
1.71 kristaps 79: struct termp *p;
1.14 kristaps 80:
1.117 kristaps 81: p = calloc(1, sizeof(struct termp));
82: if (NULL == p) {
1.120 kristaps 83: perror(NULL);
1.117 kristaps 84: exit(EXIT_FAILURE);
85: }
1.80 kristaps 86: p->maxrmargin = 78;
1.71 kristaps 87: p->enc = enc;
88: return(p);
1.14 kristaps 89: }
90:
91:
1.71 kristaps 92: /*
93: * Flush a line of text. A "line" is loosely defined as being something
94: * that should be followed by a newline, regardless of whether it's
95: * broken apart by newlines getting there. A line can also be a
96: * fragment of a columnar list.
97: *
98: * Specifically, a line is whatever's in p->buf of length p->col, which
99: * is zeroed after this function returns.
100: *
1.84 kristaps 101: * The usage of termp:flags is as follows:
1.71 kristaps 102: *
103: * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
104: * offset value. This is useful when doing columnar lists where the
105: * prior column has right-padded.
106: *
107: * - TERMP_NOBREAK: this is the most important and is used when making
108: * columns. In short: don't print a newline and instead pad to the
109: * right margin. Used in conjunction with TERMP_NOLPAD.
110: *
1.91 kristaps 111: * - TERMP_TWOSPACE: when padding, make sure there are at least two
112: * space characters of padding. Otherwise, rather break the line.
113: *
1.84 kristaps 114: * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and
115: * the line is overrun, and don't pad-right if it's underrun.
116: *
117: * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when
118: * overruning, instead save the position and continue at that point
119: * when the next invocation.
1.71 kristaps 120: *
121: * In-line line breaking:
122: *
123: * If TERMP_NOBREAK is specified and the line overruns the right
124: * margin, it will break and pad-right to the right margin after
125: * writing. If maxrmargin is violated, it will break and continue
1.114 kristaps 126: * writing from the right-margin, which will lead to the above scenario
127: * upon exit. Otherwise, the line will break at the right margin.
1.71 kristaps 128: */
129: void
130: term_flushln(struct termp *p)
1.53 kristaps 131: {
1.114 kristaps 132: int i; /* current input position in p->buf */
133: size_t vis; /* current visual position on output */
134: size_t vbl; /* number of blanks to prepend to output */
135: size_t vsz; /* visual characters to write to output */
136: size_t bp; /* visual right border position */
137: int j; /* temporary loop index */
138: size_t maxvis, mmax;
1.91 kristaps 139: static int overstep = 0;
1.53 kristaps 140:
1.71 kristaps 141: /*
142: * First, establish the maximum columns of "visible" content.
143: * This is usually the difference between the right-margin and
144: * an indentation, but can be, for tagged lists or columns, a
1.115 kristaps 145: * small set of values.
1.71 kristaps 146: */
1.53 kristaps 147:
1.71 kristaps 148: assert(p->offset < p->rmargin);
1.92 kristaps 149:
1.114 kristaps 150: maxvis = (int)(p->rmargin - p->offset) - overstep < 0 ?
1.119 kristaps 151: /* LINTED */
152: 0 : p->rmargin - p->offset - overstep;
1.114 kristaps 153: mmax = (int)(p->maxrmargin - p->offset) - overstep < 0 ?
1.119 kristaps 154: /* LINTED */
155: 0 : p->maxrmargin - p->offset - overstep;
1.92 kristaps 156:
1.71 kristaps 157: bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
1.115 kristaps 158:
159: /*
160: * FIXME: if bp is zero, we still output the first word before
161: * breaking the line.
162: */
163:
1.71 kristaps 164: vis = 0;
1.84 kristaps 165:
1.71 kristaps 166: /*
167: * If in the standard case (left-justified), then begin with our
168: * indentation, otherwise (columns, etc.) just start spitting
169: * out text.
170: */
1.53 kristaps 171:
1.71 kristaps 172: if ( ! (p->flags & TERMP_NOLPAD))
173: /* LINTED */
174: for (j = 0; j < (int)p->offset; j++)
175: putchar(' ');
176:
177: for (i = 0; i < (int)p->col; i++) {
178: /*
179: * Count up visible word characters. Control sequences
180: * (starting with the CSI) aren't counted. A space
181: * generates a non-printing word, which is valid (the
182: * space is printed according to regular spacing rules).
183: */
184:
185: /* LINTED */
186: for (j = i, vsz = 0; j < (int)p->col; j++) {
1.93 kristaps 187: if (j && ' ' == p->buf[j])
1.71 kristaps 188: break;
189: else if (8 == p->buf[j])
1.89 kristaps 190: vsz--;
1.71 kristaps 191: else
192: vsz++;
193: }
1.53 kristaps 194:
1.71 kristaps 195: /*
1.81 kristaps 196: * Choose the number of blanks to prepend: no blank at the
197: * beginning of a line, one between words -- but do not
198: * actually write them yet.
1.71 kristaps 199: */
1.81 kristaps 200: vbl = (size_t)(0 == vis ? 0 : 1);
1.71 kristaps 201:
1.81 kristaps 202: /*
203: * Find out whether we would exceed the right margin.
204: * If so, break to the next line. (TODO: hyphenate)
205: * Otherwise, write the chosen number of blanks now.
206: */
207: if (vis && vis + vbl + vsz > bp) {
208: putchar('\n');
209: if (TERMP_NOBREAK & p->flags) {
210: for (j = 0; j < (int)p->rmargin; j++)
211: putchar(' ');
212: vis = p->rmargin - p->offset;
213: } else {
1.71 kristaps 214: for (j = 0; j < (int)p->offset; j++)
215: putchar(' ');
216: vis = 0;
1.81 kristaps 217: }
1.104 kristaps 218: /* Remove the overstep width. */
1.112 kristaps 219: bp += (int)/* LINTED */
220: overstep;
1.110 kristaps 221: overstep = 0;
1.81 kristaps 222: } else {
223: for (j = 0; j < (int)vbl; j++)
1.71 kristaps 224: putchar(' ');
1.81 kristaps 225: vis += vbl;
1.71 kristaps 226: }
1.53 kristaps 227:
1.78 kristaps 228: /*
1.81 kristaps 229: * Finally, write out the word.
1.71 kristaps 230: */
231: for ( ; i < (int)p->col; i++) {
232: if (' ' == p->buf[i])
233: break;
1.121 kristaps 234:
235: /* The unit sep. is a non-breaking space. */
236: if (31 == p->buf[i])
237: putchar(' ');
238: else
239: putchar(p->buf[i]);
1.71 kristaps 240: }
241: vis += vsz;
242: }
1.111 kristaps 243:
1.91 kristaps 244: p->col = 0;
1.111 kristaps 245: overstep = 0;
1.15 kristaps 246:
1.91 kristaps 247: if ( ! (TERMP_NOBREAK & p->flags)) {
248: putchar('\n');
1.15 kristaps 249: return;
1.71 kristaps 250: }
1.15 kristaps 251:
1.91 kristaps 252: if (TERMP_HANG & p->flags) {
253: /* We need one blank after the tag. */
1.92 kristaps 254: overstep = /* LINTED */
255: vis - maxvis + 1;
1.91 kristaps 256:
257: /*
258: * Behave exactly the same way as groff:
1.92 kristaps 259: * If we have overstepped the margin, temporarily move
260: * it to the right and flag the rest of the line to be
261: * shorter.
1.91 kristaps 262: * If we landed right at the margin, be happy.
1.92 kristaps 263: * If we are one step before the margin, temporarily
264: * move it one step LEFT and flag the rest of the line
265: * to be longer.
1.91 kristaps 266: */
1.92 kristaps 267: if (overstep >= -1) {
268: assert((int)maxvis + overstep >= 0);
269: /* LINTED */
1.91 kristaps 270: maxvis += overstep;
1.92 kristaps 271: } else
1.91 kristaps 272: overstep = 0;
273:
274: } else if (TERMP_DANGLE & p->flags)
275: return;
1.15 kristaps 276:
1.92 kristaps 277: /* Right-pad. */
278: if (maxvis > vis + /* LINTED */
279: ((TERMP_TWOSPACE & p->flags) ? 1 : 0))
1.91 kristaps 280: for ( ; vis < maxvis; vis++)
281: putchar(' ');
1.92 kristaps 282: else { /* ...or newline break. */
1.71 kristaps 283: putchar('\n');
1.91 kristaps 284: for (i = 0; i < (int)p->rmargin; i++)
285: putchar(' ');
286: }
1.15 kristaps 287: }
288:
289:
1.71 kristaps 290: /*
291: * A newline only breaks an existing line; it won't assert vertical
292: * space. All data in the output buffer is flushed prior to the newline
293: * assertion.
294: */
295: void
296: term_newln(struct termp *p)
1.15 kristaps 297: {
298:
1.71 kristaps 299: p->flags |= TERMP_NOSPACE;
300: if (0 == p->col) {
301: p->flags &= ~TERMP_NOLPAD;
1.15 kristaps 302: return;
1.16 kristaps 303: }
1.71 kristaps 304: term_flushln(p);
305: p->flags &= ~TERMP_NOLPAD;
1.16 kristaps 306: }
307:
308:
1.71 kristaps 309: /*
310: * Asserts a vertical space (a full, empty line-break between lines).
311: * Note that if used twice, this will cause two blank spaces and so on.
312: * All data in the output buffer is flushed prior to the newline
313: * assertion.
314: */
315: void
316: term_vspace(struct termp *p)
1.16 kristaps 317: {
318:
1.62 kristaps 319: term_newln(p);
1.71 kristaps 320: putchar('\n');
1.16 kristaps 321: }
322:
323:
1.71 kristaps 324: static void
1.125 kristaps 325: spec(struct termp *p, const char *word, size_t len)
1.17 kristaps 326: {
1.71 kristaps 327: const char *rhs;
328: size_t sz;
1.17 kristaps 329:
1.101 kristaps 330: rhs = chars_a2ascii(p->symtab, word, len, &sz);
1.125 kristaps 331: if (rhs)
332: encode(p, rhs, sz);
1.94 kristaps 333: }
334:
335:
336: static void
1.125 kristaps 337: res(struct termp *p, const char *word, size_t len)
1.94 kristaps 338: {
339: const char *rhs;
340: size_t sz;
341:
1.101 kristaps 342: rhs = chars_a2res(p->symtab, word, len, &sz);
1.125 kristaps 343: if (rhs)
344: encode(p, rhs, sz);
345: }
346:
347:
348: void
349: term_fontlast(struct termp *p)
350: {
351: enum termfont f;
352:
353: f = p->fontl;
354: p->fontl = p->fontq[p->fonti];
355: p->fontq[p->fonti] = f;
356: }
357:
358:
359: void
360: term_fontrepl(struct termp *p, enum termfont f)
361: {
362:
363: p->fontl = p->fontq[p->fonti];
364: p->fontq[p->fonti] = f;
365: }
366:
367:
368: void
369: term_fontpush(struct termp *p, enum termfont f)
370: {
371:
372: assert(p->fonti + 1 < 10);
373: p->fontl = p->fontq[p->fonti];
374: p->fontq[++p->fonti] = f;
375: }
376:
377:
378: const void *
379: term_fontq(struct termp *p)
380: {
381:
382: return(&p->fontq[p->fonti]);
383: }
384:
385:
386: enum termfont
387: term_fonttop(struct termp *p)
388: {
389:
390: return(p->fontq[p->fonti]);
391: }
392:
393:
394: void
395: term_fontpopq(struct termp *p, const void *key)
396: {
397:
398: while (p->fonti >= 0 && key != &p->fontq[p->fonti])
399: p->fonti--;
400: assert(p->fonti >= 0);
401: }
1.94 kristaps 402:
1.125 kristaps 403:
404: void
405: term_fontpop(struct termp *p)
406: {
407:
408: assert(p->fonti);
409: p->fonti--;
1.17 kristaps 410: }
411:
412:
1.71 kristaps 413: /*
414: * Handle pwords, partial words, which may be either a single word or a
415: * phrase that cannot be broken down (such as a literal string). This
416: * handles word styling.
417: */
1.86 kristaps 418: void
419: term_word(struct termp *p, const char *word)
1.65 kristaps 420: {
1.124 kristaps 421: const char *sv, *seq;
1.125 kristaps 422: int sz;
1.124 kristaps 423: size_t ssz;
424: enum roffdeco deco;
1.71 kristaps 425:
1.100 kristaps 426: sv = word;
427:
1.123 kristaps 428: if (word[0] && '\0' == word[1])
1.100 kristaps 429: switch (word[0]) {
430: case('.'):
431: /* FALLTHROUGH */
432: case(','):
433: /* FALLTHROUGH */
434: case(';'):
435: /* FALLTHROUGH */
436: case(':'):
437: /* FALLTHROUGH */
438: case('?'):
439: /* FALLTHROUGH */
440: case('!'):
441: /* FALLTHROUGH */
442: case(')'):
443: /* FALLTHROUGH */
444: case(']'):
445: /* FALLTHROUGH */
446: case('}'):
447: if ( ! (TERMP_IGNDELIM & p->flags))
448: p->flags |= TERMP_NOSPACE;
449: break;
450: default:
451: break;
452: }
1.65 kristaps 453:
1.71 kristaps 454: if ( ! (TERMP_NOSPACE & p->flags))
1.125 kristaps 455: bufferc(p, ' ');
1.65 kristaps 456:
1.71 kristaps 457: if ( ! (p->flags & TERMP_NONOSPACE))
458: p->flags &= ~TERMP_NOSPACE;
1.65 kristaps 459:
1.125 kristaps 460: /* FIXME: use strcspn. */
1.124 kristaps 461:
462: while (*word) {
463: if ('\\' != *word) {
1.125 kristaps 464: encode(p, word, 1);
1.124 kristaps 465: word++;
466: continue;
467: }
468:
469: seq = ++word;
470: sz = a2roffdeco(&deco, &seq, &ssz);
471:
472: switch (deco) {
473: case (DECO_RESERVED):
1.125 kristaps 474: res(p, seq, ssz);
1.124 kristaps 475: break;
476: case (DECO_SPECIAL):
1.125 kristaps 477: spec(p, seq, ssz);
1.124 kristaps 478: break;
479: case (DECO_BOLD):
1.125 kristaps 480: term_fontrepl(p, TERMFONT_BOLD);
1.124 kristaps 481: break;
482: case (DECO_ITALIC):
1.125 kristaps 483: term_fontrepl(p, TERMFONT_UNDER);
1.124 kristaps 484: break;
485: case (DECO_ROMAN):
1.125 kristaps 486: term_fontrepl(p, TERMFONT_NONE);
1.124 kristaps 487: break;
488: case (DECO_PREVIOUS):
1.125 kristaps 489: term_fontlast(p);
1.124 kristaps 490: break;
491: default:
492: break;
493: }
1.127 kristaps 494:
1.124 kristaps 495: word += sz;
1.127 kristaps 496: if (DECO_NOSPACE == deco && '\0' == *word)
497: p->flags |= TERMP_NOSPACE;
1.124 kristaps 498: }
1.65 kristaps 499:
1.100 kristaps 500: if (sv[0] && 0 == sv[1])
501: switch (sv[0]) {
502: case('('):
503: /* FALLTHROUGH */
504: case('['):
505: /* FALLTHROUGH */
506: case('{'):
507: p->flags |= TERMP_NOSPACE;
508: break;
509: default:
510: break;
511: }
1.65 kristaps 512: }
513:
514:
1.71 kristaps 515: static void
1.125 kristaps 516: adjbuf(struct termp *p, size_t sz)
1.51 kristaps 517: {
518:
1.125 kristaps 519: if (0 == p->maxcols)
520: p->maxcols = 1024;
521: while (sz >= p->maxcols)
522: p->maxcols <<= 2;
523:
524: p->buf = realloc(p->buf, p->maxcols);
525: if (NULL == p->buf) {
526: perror(NULL);
527: exit(EXIT_FAILURE);
1.71 kristaps 528: }
1.51 kristaps 529: }
530:
1.79 kristaps 531:
532: static void
1.125 kristaps 533: buffera(struct termp *p, const char *word, size_t sz)
1.79 kristaps 534: {
1.125 kristaps 535:
536: if (p->col + sz >= p->maxcols)
537: adjbuf(p, p->col + sz);
538:
1.126 kristaps 539: memcpy(&p->buf[(int)p->col], word, sz);
1.125 kristaps 540: p->col += sz;
541: }
542:
543:
544: static void
545: bufferc(struct termp *p, char c)
546: {
547:
548: if (p->col + 1 >= p->maxcols)
549: adjbuf(p, p->col + 1);
550:
1.126 kristaps 551: p->buf[(int)p->col++] = c;
1.125 kristaps 552: }
553:
554:
555: static void
556: encode(struct termp *p, const char *word, size_t sz)
557: {
558: enum termfont f;
559: int i;
560:
561: /*
562: * Encode and buffer a string of characters. If the current
563: * font mode is unset, buffer directly, else encode then buffer
564: * character by character.
565: */
566:
567: if (TERMFONT_NONE == (f = term_fonttop(p))) {
568: buffera(p, word, sz);
569: return;
570: }
571:
572: for (i = 0; i < (int)sz; i++) {
573: if ( ! isgraph((u_char)word[i])) {
574: bufferc(p, word[i]);
575: continue;
1.79 kristaps 576: }
1.125 kristaps 577:
578: if (TERMFONT_UNDER == f)
579: bufferc(p, '_');
580: else
581: bufferc(p, word[i]);
582:
583: bufferc(p, 8);
584: bufferc(p, word[i]);
1.79 kristaps 585: }
586: }
1.106 kristaps 587:
588:
1.107 kristaps 589: size_t
590: term_vspan(const struct roffsu *su)
1.106 kristaps 591: {
592: double r;
593:
1.107 kristaps 594: switch (su->unit) {
1.106 kristaps 595: case (SCALE_CM):
1.107 kristaps 596: r = su->scale * 2;
1.106 kristaps 597: break;
598: case (SCALE_IN):
1.107 kristaps 599: r = su->scale * 6;
1.106 kristaps 600: break;
601: case (SCALE_PC):
1.107 kristaps 602: r = su->scale;
1.106 kristaps 603: break;
604: case (SCALE_PT):
1.107 kristaps 605: r = su->scale / 8;
1.106 kristaps 606: break;
607: case (SCALE_MM):
1.107 kristaps 608: r = su->scale / 1000;
1.106 kristaps 609: break;
610: case (SCALE_VS):
1.107 kristaps 611: r = su->scale;
1.106 kristaps 612: break;
613: default:
1.107 kristaps 614: r = su->scale - 1;
1.106 kristaps 615: break;
616: }
617:
618: if (r < 0.0)
619: r = 0.0;
1.107 kristaps 620: return(/* LINTED */(size_t)
1.106 kristaps 621: r);
622: }
623:
624:
1.107 kristaps 625: size_t
626: term_hspan(const struct roffsu *su)
1.106 kristaps 627: {
628: double r;
629:
1.108 kristaps 630: /* XXX: CM, IN, and PT are approximations. */
631:
1.107 kristaps 632: switch (su->unit) {
1.106 kristaps 633: case (SCALE_CM):
1.108 kristaps 634: r = 4 * su->scale;
1.106 kristaps 635: break;
636: case (SCALE_IN):
1.108 kristaps 637: /* XXX: this is an approximation. */
638: r = 10 * su->scale;
1.106 kristaps 639: break;
640: case (SCALE_PC):
1.108 kristaps 641: r = (10 * su->scale) / 6;
1.106 kristaps 642: break;
643: case (SCALE_PT):
1.108 kristaps 644: r = (10 * su->scale) / 72;
1.106 kristaps 645: break;
646: case (SCALE_MM):
1.107 kristaps 647: r = su->scale / 1000; /* FIXME: double-check. */
1.106 kristaps 648: break;
649: case (SCALE_VS):
1.107 kristaps 650: r = su->scale * 2 - 1; /* FIXME: double-check. */
1.106 kristaps 651: break;
652: default:
1.107 kristaps 653: r = su->scale;
1.106 kristaps 654: break;
655: }
656:
657: if (r < 0.0)
658: r = 0.0;
1.107 kristaps 659: return((size_t)/* LINTED */
1.106 kristaps 660: r);
661: }
662:
663:
CVSweb