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