Annotation of mandoc/term.c, Revision 1.136
1.136 ! schwarze 1: /* $Id: term.c,v 1.135 2010/05/16 01:35:37 schwarze 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.134 joerg 37: static struct termp *term_alloc(enum termenc, size_t);
1.71 kristaps 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 *
1.134 joerg 48: ascii_alloc(size_t width)
1.10 kristaps 49: {
1.1 kristaps 50:
1.134 joerg 51: return(term_alloc(TERMENC_ASCII, width));
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 *
1.134 joerg 77: term_alloc(enum termenc enc, size_t width)
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.71 kristaps 86: p->enc = enc;
1.134 joerg 87: /* Enforce some lower boundary. */
88: if (width < 60)
89: width = 60;
90: p->defrmargin = width - 2;
1.71 kristaps 91: return(p);
1.14 kristaps 92: }
93:
94:
1.71 kristaps 95: /*
96: * Flush a line of text. A "line" is loosely defined as being something
97: * that should be followed by a newline, regardless of whether it's
98: * broken apart by newlines getting there. A line can also be a
1.130 kristaps 99: * fragment of a columnar list (`Bl -tag' or `Bl -column'), which does
100: * not have a trailing newline.
1.71 kristaps 101: *
1.130 kristaps 102: * The following flags may be specified:
1.71 kristaps 103: *
104: * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
105: * offset value. This is useful when doing columnar lists where the
106: * prior column has right-padded.
107: *
108: * - TERMP_NOBREAK: this is the most important and is used when making
109: * columns. In short: don't print a newline and instead pad to the
110: * right margin. Used in conjunction with TERMP_NOLPAD.
111: *
1.91 kristaps 112: * - TERMP_TWOSPACE: when padding, make sure there are at least two
113: * space characters of padding. Otherwise, rather break the line.
114: *
1.84 kristaps 115: * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and
116: * the line is overrun, and don't pad-right if it's underrun.
117: *
118: * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when
119: * overruning, instead save the position and continue at that point
120: * when the next invocation.
1.71 kristaps 121: *
122: * In-line line breaking:
123: *
124: * If TERMP_NOBREAK is specified and the line overruns the right
125: * margin, it will break and pad-right to the right margin after
126: * writing. If maxrmargin is violated, it will break and continue
1.114 kristaps 127: * writing from the right-margin, which will lead to the above scenario
128: * upon exit. Otherwise, the line will break at the right margin.
1.71 kristaps 129: */
130: void
131: term_flushln(struct termp *p)
1.53 kristaps 132: {
1.114 kristaps 133: int i; /* current input position in p->buf */
134: size_t vis; /* current visual position on output */
135: size_t vbl; /* number of blanks to prepend to output */
1.136 ! schwarze 136: size_t vend; /* end of word visual position on output */
1.114 kristaps 137: size_t bp; /* visual right border position */
138: int j; /* temporary loop index */
139: size_t maxvis, mmax;
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.129 kristaps 150: maxvis = (int)(p->rmargin - p->offset) - p->overstep < 0 ?
1.119 kristaps 151: /* LINTED */
1.129 kristaps 152: 0 : p->rmargin - p->offset - p->overstep;
153: mmax = (int)(p->maxrmargin - p->offset) - p->overstep < 0 ?
1.119 kristaps 154: /* LINTED */
1.129 kristaps 155: 0 : p->maxrmargin - p->offset - p->overstep;
1.92 kristaps 156:
1.71 kristaps 157: bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
1.115 kristaps 158:
1.136 ! schwarze 159: /*
! 160: * Indent the first line of a paragraph.
! 161: */
! 162: vbl = p->flags & TERMP_NOLPAD ? 0 : p->offset;
! 163:
1.115 kristaps 164: /*
165: * FIXME: if bp is zero, we still output the first word before
166: * breaking the line.
167: */
168:
1.136 ! schwarze 169: vis = vend = i = 0;
! 170: while (i < (int)p->col) {
1.71 kristaps 171:
172: /*
173: * Count up visible word characters. Control sequences
174: * (starting with the CSI) aren't counted. A space
175: * generates a non-printing word, which is valid (the
176: * space is printed according to regular spacing rules).
177: */
178:
179: /* LINTED */
1.136 ! schwarze 180: for (j = i; j < (int)p->col; j++) {
1.93 kristaps 181: if (j && ' ' == p->buf[j])
1.71 kristaps 182: break;
1.130 kristaps 183: if (8 == p->buf[j])
1.136 ! schwarze 184: vend--;
1.71 kristaps 185: else
1.136 ! schwarze 186: vend++;
1.71 kristaps 187: }
1.53 kristaps 188:
1.71 kristaps 189: /*
1.81 kristaps 190: * Find out whether we would exceed the right margin.
1.136 ! schwarze 191: * If so, break to the next line.
1.81 kristaps 192: */
1.136 ! schwarze 193: if (vend > bp && vis > 0) {
! 194: vend -= vis;
1.81 kristaps 195: putchar('\n');
196: if (TERMP_NOBREAK & p->flags) {
197: for (j = 0; j < (int)p->rmargin; j++)
198: putchar(' ');
1.136 ! schwarze 199: vend += p->rmargin - p->offset;
1.81 kristaps 200: } else {
1.136 ! schwarze 201: vbl = p->offset;
1.81 kristaps 202: }
1.130 kristaps 203:
1.129 kristaps 204: /* Remove the p->overstep width. */
1.130 kristaps 205:
1.112 kristaps 206: bp += (int)/* LINTED */
1.129 kristaps 207: p->overstep;
208: p->overstep = 0;
1.71 kristaps 209: }
1.53 kristaps 210:
1.130 kristaps 211: /* Write out the [remaining] word. */
1.136 ! schwarze 212: for ( ; i < (int)p->col; i++) {
! 213: if (' ' == p->buf[i]) {
! 214: while (' ' == p->buf[i]) {
! 215: vbl++;
! 216: i++;
! 217: }
1.71 kristaps 218: break;
1.136 ! schwarze 219: }
! 220: if (ASCII_NBRSP == p->buf[i]) {
! 221: vbl++;
! 222: continue;
! 223: }
1.130 kristaps 224:
1.136 ! schwarze 225: /*
! 226: * Now we definitely know there will be
! 227: * printable characters to output,
! 228: * so write preceding white space now.
! 229: */
! 230: if (vbl) {
! 231: for (j = 0; j < (int)vbl; j++)
! 232: putchar(' ');
! 233: vbl = 0;
! 234: }
! 235: putchar(p->buf[i]);
! 236: }
! 237: vend += vbl;
! 238: vis = vend;
1.71 kristaps 239: }
1.111 kristaps 240:
1.91 kristaps 241: p->col = 0;
1.129 kristaps 242: p->overstep = 0;
1.15 kristaps 243:
1.91 kristaps 244: if ( ! (TERMP_NOBREAK & p->flags)) {
245: putchar('\n');
1.15 kristaps 246: return;
1.71 kristaps 247: }
1.15 kristaps 248:
1.91 kristaps 249: if (TERMP_HANG & p->flags) {
250: /* We need one blank after the tag. */
1.129 kristaps 251: p->overstep = /* LINTED */
1.92 kristaps 252: vis - maxvis + 1;
1.91 kristaps 253:
254: /*
255: * Behave exactly the same way as groff:
1.92 kristaps 256: * If we have overstepped the margin, temporarily move
257: * it to the right and flag the rest of the line to be
258: * shorter.
1.91 kristaps 259: * If we landed right at the margin, be happy.
1.92 kristaps 260: * If we are one step before the margin, temporarily
261: * move it one step LEFT and flag the rest of the line
262: * to be longer.
1.91 kristaps 263: */
1.129 kristaps 264: if (p->overstep >= -1) {
265: assert((int)maxvis + p->overstep >= 0);
1.92 kristaps 266: /* LINTED */
1.129 kristaps 267: maxvis += p->overstep;
1.92 kristaps 268: } else
1.129 kristaps 269: p->overstep = 0;
1.91 kristaps 270:
271: } else if (TERMP_DANGLE & p->flags)
272: return;
1.15 kristaps 273:
1.92 kristaps 274: /* Right-pad. */
275: if (maxvis > vis + /* LINTED */
276: ((TERMP_TWOSPACE & p->flags) ? 1 : 0))
1.91 kristaps 277: for ( ; vis < maxvis; vis++)
278: putchar(' ');
1.92 kristaps 279: else { /* ...or newline break. */
1.71 kristaps 280: putchar('\n');
1.91 kristaps 281: for (i = 0; i < (int)p->rmargin; i++)
282: putchar(' ');
283: }
1.15 kristaps 284: }
285:
286:
1.71 kristaps 287: /*
288: * A newline only breaks an existing line; it won't assert vertical
289: * space. All data in the output buffer is flushed prior to the newline
290: * assertion.
291: */
292: void
293: term_newln(struct termp *p)
1.15 kristaps 294: {
295:
1.71 kristaps 296: p->flags |= TERMP_NOSPACE;
297: if (0 == p->col) {
298: p->flags &= ~TERMP_NOLPAD;
1.15 kristaps 299: return;
1.16 kristaps 300: }
1.71 kristaps 301: term_flushln(p);
302: p->flags &= ~TERMP_NOLPAD;
1.16 kristaps 303: }
304:
305:
1.71 kristaps 306: /*
307: * Asserts a vertical space (a full, empty line-break between lines).
308: * Note that if used twice, this will cause two blank spaces and so on.
309: * All data in the output buffer is flushed prior to the newline
310: * assertion.
311: */
312: void
313: term_vspace(struct termp *p)
1.16 kristaps 314: {
315:
1.62 kristaps 316: term_newln(p);
1.71 kristaps 317: putchar('\n');
1.16 kristaps 318: }
319:
320:
1.71 kristaps 321: static void
1.125 kristaps 322: spec(struct termp *p, const char *word, size_t len)
1.17 kristaps 323: {
1.71 kristaps 324: const char *rhs;
325: size_t sz;
1.17 kristaps 326:
1.101 kristaps 327: rhs = chars_a2ascii(p->symtab, word, len, &sz);
1.125 kristaps 328: if (rhs)
329: encode(p, rhs, sz);
1.94 kristaps 330: }
331:
332:
333: static void
1.125 kristaps 334: res(struct termp *p, const char *word, size_t len)
1.94 kristaps 335: {
336: const char *rhs;
337: size_t sz;
338:
1.101 kristaps 339: rhs = chars_a2res(p->symtab, word, len, &sz);
1.125 kristaps 340: if (rhs)
341: encode(p, rhs, sz);
342: }
343:
344:
345: void
346: term_fontlast(struct termp *p)
347: {
348: enum termfont f;
349:
350: f = p->fontl;
351: p->fontl = p->fontq[p->fonti];
352: p->fontq[p->fonti] = f;
353: }
354:
355:
356: void
357: term_fontrepl(struct termp *p, enum termfont f)
358: {
359:
360: p->fontl = p->fontq[p->fonti];
361: p->fontq[p->fonti] = f;
362: }
363:
364:
365: void
366: term_fontpush(struct termp *p, enum termfont f)
367: {
368:
369: assert(p->fonti + 1 < 10);
370: p->fontl = p->fontq[p->fonti];
371: p->fontq[++p->fonti] = f;
372: }
373:
374:
375: const void *
376: term_fontq(struct termp *p)
377: {
378:
379: return(&p->fontq[p->fonti]);
380: }
381:
382:
383: enum termfont
384: term_fonttop(struct termp *p)
385: {
386:
387: return(p->fontq[p->fonti]);
388: }
389:
390:
391: void
392: term_fontpopq(struct termp *p, const void *key)
393: {
394:
395: while (p->fonti >= 0 && key != &p->fontq[p->fonti])
396: p->fonti--;
397: assert(p->fonti >= 0);
398: }
1.94 kristaps 399:
1.125 kristaps 400:
401: void
402: term_fontpop(struct termp *p)
403: {
404:
405: assert(p->fonti);
406: p->fonti--;
1.17 kristaps 407: }
408:
409:
1.71 kristaps 410: /*
411: * Handle pwords, partial words, which may be either a single word or a
412: * phrase that cannot be broken down (such as a literal string). This
413: * handles word styling.
414: */
1.86 kristaps 415: void
416: term_word(struct termp *p, const char *word)
1.65 kristaps 417: {
1.124 kristaps 418: const char *sv, *seq;
1.125 kristaps 419: int sz;
1.124 kristaps 420: size_t ssz;
421: enum roffdeco deco;
1.71 kristaps 422:
1.100 kristaps 423: sv = word;
424:
1.123 kristaps 425: if (word[0] && '\0' == word[1])
1.100 kristaps 426: switch (word[0]) {
427: case('.'):
428: /* FALLTHROUGH */
429: case(','):
430: /* FALLTHROUGH */
431: case(';'):
432: /* FALLTHROUGH */
433: case(':'):
434: /* FALLTHROUGH */
435: case('?'):
436: /* FALLTHROUGH */
437: case('!'):
438: /* FALLTHROUGH */
439: case(')'):
440: /* FALLTHROUGH */
441: case(']'):
442: if ( ! (TERMP_IGNDELIM & p->flags))
443: p->flags |= TERMP_NOSPACE;
444: break;
445: default:
446: break;
447: }
1.65 kristaps 448:
1.133 kristaps 449: if ( ! (TERMP_NOSPACE & p->flags)) {
1.125 kristaps 450: bufferc(p, ' ');
1.133 kristaps 451: if (TERMP_SENTENCE & p->flags)
452: bufferc(p, ' ');
453: }
1.65 kristaps 454:
1.71 kristaps 455: if ( ! (p->flags & TERMP_NONOSPACE))
456: p->flags &= ~TERMP_NOSPACE;
1.133 kristaps 457:
458: p->flags &= ~TERMP_SENTENCE;
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.131 kristaps 500: /*
501: * Note that we don't process the pipe: the parser sees it as
502: * punctuation, but we don't in terms of typography.
503: */
1.100 kristaps 504: if (sv[0] && 0 == sv[1])
505: switch (sv[0]) {
506: case('('):
507: /* FALLTHROUGH */
508: case('['):
509: p->flags |= TERMP_NOSPACE;
510: break;
511: default:
512: break;
513: }
1.65 kristaps 514: }
515:
516:
1.71 kristaps 517: static void
1.125 kristaps 518: adjbuf(struct termp *p, size_t sz)
1.51 kristaps 519: {
520:
1.125 kristaps 521: if (0 == p->maxcols)
522: p->maxcols = 1024;
523: while (sz >= p->maxcols)
524: p->maxcols <<= 2;
525:
526: p->buf = realloc(p->buf, p->maxcols);
527: if (NULL == p->buf) {
528: perror(NULL);
529: exit(EXIT_FAILURE);
1.71 kristaps 530: }
1.51 kristaps 531: }
532:
1.79 kristaps 533:
534: static void
1.125 kristaps 535: buffera(struct termp *p, const char *word, size_t sz)
1.79 kristaps 536: {
1.125 kristaps 537:
538: if (p->col + sz >= p->maxcols)
539: adjbuf(p, p->col + sz);
540:
1.126 kristaps 541: memcpy(&p->buf[(int)p->col], word, sz);
1.125 kristaps 542: p->col += sz;
543: }
544:
545:
546: static void
547: bufferc(struct termp *p, char c)
548: {
549:
550: if (p->col + 1 >= p->maxcols)
551: adjbuf(p, p->col + 1);
552:
1.126 kristaps 553: p->buf[(int)p->col++] = c;
1.125 kristaps 554: }
555:
556:
557: static void
558: encode(struct termp *p, const char *word, size_t sz)
559: {
560: enum termfont f;
561: int i;
562:
563: /*
564: * Encode and buffer a string of characters. If the current
565: * font mode is unset, buffer directly, else encode then buffer
566: * character by character.
567: */
568:
569: if (TERMFONT_NONE == (f = term_fonttop(p))) {
570: buffera(p, word, sz);
571: return;
572: }
573:
574: for (i = 0; i < (int)sz; i++) {
575: if ( ! isgraph((u_char)word[i])) {
576: bufferc(p, word[i]);
577: continue;
1.79 kristaps 578: }
1.125 kristaps 579:
580: if (TERMFONT_UNDER == f)
581: bufferc(p, '_');
582: else
583: bufferc(p, word[i]);
584:
585: bufferc(p, 8);
586: bufferc(p, word[i]);
1.79 kristaps 587: }
588: }
1.106 kristaps 589:
590:
1.107 kristaps 591: size_t
592: term_vspan(const struct roffsu *su)
1.106 kristaps 593: {
594: double r;
595:
1.107 kristaps 596: switch (su->unit) {
1.106 kristaps 597: case (SCALE_CM):
1.107 kristaps 598: r = su->scale * 2;
1.106 kristaps 599: break;
600: case (SCALE_IN):
1.107 kristaps 601: r = su->scale * 6;
1.106 kristaps 602: break;
603: case (SCALE_PC):
1.107 kristaps 604: r = su->scale;
1.106 kristaps 605: break;
606: case (SCALE_PT):
1.107 kristaps 607: r = su->scale / 8;
1.106 kristaps 608: break;
609: case (SCALE_MM):
1.107 kristaps 610: r = su->scale / 1000;
1.106 kristaps 611: break;
612: case (SCALE_VS):
1.107 kristaps 613: r = su->scale;
1.106 kristaps 614: break;
615: default:
1.107 kristaps 616: r = su->scale - 1;
1.106 kristaps 617: break;
618: }
619:
620: if (r < 0.0)
621: r = 0.0;
1.107 kristaps 622: return(/* LINTED */(size_t)
1.106 kristaps 623: r);
624: }
625:
626:
1.107 kristaps 627: size_t
628: term_hspan(const struct roffsu *su)
1.106 kristaps 629: {
630: double r;
631:
1.108 kristaps 632: /* XXX: CM, IN, and PT are approximations. */
633:
1.107 kristaps 634: switch (su->unit) {
1.106 kristaps 635: case (SCALE_CM):
1.108 kristaps 636: r = 4 * su->scale;
1.106 kristaps 637: break;
638: case (SCALE_IN):
1.108 kristaps 639: /* XXX: this is an approximation. */
640: r = 10 * su->scale;
1.106 kristaps 641: break;
642: case (SCALE_PC):
1.108 kristaps 643: r = (10 * su->scale) / 6;
1.106 kristaps 644: break;
645: case (SCALE_PT):
1.108 kristaps 646: r = (10 * su->scale) / 72;
1.106 kristaps 647: break;
648: case (SCALE_MM):
1.107 kristaps 649: r = su->scale / 1000; /* FIXME: double-check. */
1.106 kristaps 650: break;
651: case (SCALE_VS):
1.107 kristaps 652: r = su->scale * 2 - 1; /* FIXME: double-check. */
1.106 kristaps 653: break;
654: default:
1.107 kristaps 655: r = su->scale;
1.106 kristaps 656: break;
657: }
658:
659: if (r < 0.0)
660: r = 0.0;
1.107 kristaps 661: return((size_t)/* LINTED */
1.106 kristaps 662: r);
663: }
664:
665:
CVSweb