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