Annotation of src/usr.bin/mandoc/term.c, Revision 1.23
1.23 ! schwarze 1: /* $Id: term.c,v 1.22 2010/03/05 20:46:48 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.23 ! schwarze 136: static int line_started = 0;
1.9 schwarze 137: static int overstep = 0;
1.1 kristaps 138:
139: /*
140: * First, establish the maximum columns of "visible" content.
141: * This is usually the difference between the right-margin and
142: * an indentation, but can be, for tagged lists or columns, a
1.19 schwarze 143: * small set of values.
1.1 kristaps 144: */
145:
146: assert(p->offset < p->rmargin);
1.9 schwarze 147:
1.19 schwarze 148: maxvis = (int)(p->rmargin - p->offset) - overstep < 0 ?
149: /* LINTED */
150: 0 : p->rmargin - p->offset - overstep;
151: mmax = (int)(p->maxrmargin - p->offset) - overstep < 0 ?
152: /* LINTED */
153: 0 : p->maxrmargin - p->offset - overstep;
1.9 schwarze 154:
1.1 kristaps 155: bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
1.19 schwarze 156:
157: /*
158: * FIXME: if bp is zero, we still output the first word before
159: * breaking the line.
160: */
161:
1.22 schwarze 162: vis = vend = i = 0;
163: while (i < (int)p->col) {
164:
165: /*
166: * Choose the number of blanks to prepend: no blank at the
167: * beginning of a line, one between words -- but do not
168: * actually write them yet.
169: */
170: vbl = (size_t)(ASCII_EOS == p->buf[i] ? 2 :
171: (0 == vis ? 0 : 1));
172: vis += vbl;
173:
1.1 kristaps 174: /*
175: * Count up visible word characters. Control sequences
176: * (starting with the CSI) aren't counted. A space
177: * generates a non-printing word, which is valid (the
178: * space is printed according to regular spacing rules).
179: */
180:
181: /* LINTED */
1.22 schwarze 182: for (j = i, jhy = 0, vend = vis; j < (int)p->col; j++) {
1.10 schwarze 183: if (j && ' ' == p->buf[j])
1.1 kristaps 184: break;
185: else if (8 == p->buf[j])
1.22 schwarze 186: vend--;
187: else if (ASCII_EOS != p->buf[j]) {
188: if (vend > vis && vend < bp &&
189: '-' == p->buf[j])
190: jhy = j;
191: vend++;
192: }
1.1 kristaps 193: }
194:
195: /*
1.21 schwarze 196: * Skip empty words. This happens due to the ASCII_EOS
197: * after the end of the final sentence of a paragraph.
198: */
1.22 schwarze 199: if (vend == vis && j == (int)p->col)
1.21 schwarze 200: break;
201:
202: /*
1.23 ! schwarze 203: * Usually, indent the first line of each paragraph.
! 204: */
! 205: if (0 == i && ! (p->flags & TERMP_NOLPAD))
! 206: /* LINTED */
! 207: for (j = 0; j < (int)p->offset; j++)
! 208: putchar(' ');
! 209: line_started = 1;
! 210:
! 211: /*
1.5 schwarze 212: * Find out whether we would exceed the right margin.
213: * If so, break to the next line. (TODO: hyphenate)
214: * Otherwise, write the chosen number of blanks now.
215: */
1.22 schwarze 216: if (vend > bp && 0 == jhy) {
217: vend -= vis;
1.5 schwarze 218: putchar('\n');
219: if (TERMP_NOBREAK & p->flags) {
220: for (j = 0; j < (int)p->rmargin; j++)
221: putchar(' ');
1.22 schwarze 222: vend += p->rmargin - p->offset;
1.5 schwarze 223: } else {
1.1 kristaps 224: for (j = 0; j < (int)p->offset; j++)
225: putchar(' ');
1.5 schwarze 226: }
1.16 schwarze 227: /* Remove the overstep width. */
1.18 schwarze 228: bp += (int)/* LINTED */
229: overstep;
1.17 schwarze 230: overstep = 0;
1.5 schwarze 231: } else {
232: for (j = 0; j < (int)vbl; j++)
1.1 kristaps 233: putchar(' ');
234: }
235:
1.3 schwarze 236: /*
1.5 schwarze 237: * Finally, write out the word.
1.1 kristaps 238: */
239: for ( ; i < (int)p->col; i++) {
1.22 schwarze 240: if (vend > bp && i > jhy)
1.1 kristaps 241: break;
1.22 schwarze 242: if (' ' == p->buf[i]) {
243: i++;
244: break;
245: }
1.21 schwarze 246: if (ASCII_NBRSP == p->buf[i])
1.20 schwarze 247: putchar(' ');
1.21 schwarze 248: else if (ASCII_EOS != p->buf[i])
1.20 schwarze 249: putchar(p->buf[i]);
1.1 kristaps 250: }
1.22 schwarze 251: vis = vend;
1.1 kristaps 252: }
1.18 schwarze 253:
1.9 schwarze 254: p->col = 0;
1.18 schwarze 255: overstep = 0;
1.1 kristaps 256:
1.9 schwarze 257: if ( ! (TERMP_NOBREAK & p->flags)) {
1.23 ! schwarze 258: if (line_started) {
! 259: putchar('\n');
! 260: line_started = 0;
! 261: }
1.1 kristaps 262: return;
263: }
264:
1.9 schwarze 265: if (TERMP_HANG & p->flags) {
266: /* We need one blank after the tag. */
267: overstep = /* LINTED */
268: vis - maxvis + 1;
269:
270: /*
271: * Behave exactly the same way as groff:
272: * If we have overstepped the margin, temporarily move
273: * it to the right and flag the rest of the line to be
274: * shorter.
275: * If we landed right at the margin, be happy.
276: * If we are one step before the margin, temporarily
277: * move it one step LEFT and flag the rest of the line
278: * to be longer.
279: */
280: if (overstep >= -1) {
281: assert((int)maxvis + overstep >= 0);
282: /* LINTED */
283: maxvis += overstep;
284: } else
285: overstep = 0;
286:
287: } else if (TERMP_DANGLE & p->flags)
288: return;
1.1 kristaps 289:
1.9 schwarze 290: /* Right-pad. */
291: if (maxvis > vis + /* LINTED */
292: ((TERMP_TWOSPACE & p->flags) ? 1 : 0))
293: for ( ; vis < maxvis; vis++)
294: putchar(' ');
295: else { /* ...or newline break. */
1.1 kristaps 296: putchar('\n');
1.9 schwarze 297: for (i = 0; i < (int)p->rmargin; i++)
298: putchar(' ');
299: }
1.1 kristaps 300: }
301:
302:
303: /*
304: * A newline only breaks an existing line; it won't assert vertical
305: * space. All data in the output buffer is flushed prior to the newline
306: * assertion.
307: */
308: void
309: term_newln(struct termp *p)
310: {
311:
312: p->flags |= TERMP_NOSPACE;
313: if (0 == p->col) {
314: p->flags &= ~TERMP_NOLPAD;
315: return;
316: }
317: term_flushln(p);
318: p->flags &= ~TERMP_NOLPAD;
319: }
320:
321:
322: /*
323: * Asserts a vertical space (a full, empty line-break between lines).
324: * Note that if used twice, this will cause two blank spaces and so on.
325: * All data in the output buffer is flushed prior to the newline
326: * assertion.
327: */
328: void
329: term_vspace(struct termp *p)
330: {
331:
332: term_newln(p);
333: putchar('\n');
334: }
335:
336:
337: static void
1.20 schwarze 338: spec(struct termp *p, const char *word, size_t len)
1.1 kristaps 339: {
340: const char *rhs;
341: size_t sz;
342:
1.15 schwarze 343: rhs = chars_a2ascii(p->symtab, word, len, &sz);
1.20 schwarze 344: if (rhs)
345: encode(p, rhs, sz);
1.11 schwarze 346: }
347:
348:
349: static void
1.20 schwarze 350: res(struct termp *p, const char *word, size_t len)
1.11 schwarze 351: {
352: const char *rhs;
353: size_t sz;
354:
1.15 schwarze 355: rhs = chars_a2res(p->symtab, word, len, &sz);
1.20 schwarze 356: if (rhs)
357: encode(p, rhs, sz);
358: }
359:
360:
361: void
362: term_fontlast(struct termp *p)
363: {
364: enum termfont f;
1.11 schwarze 365:
1.20 schwarze 366: f = p->fontl;
367: p->fontl = p->fontq[p->fonti];
368: p->fontq[p->fonti] = f;
369: }
370:
371:
372: void
373: term_fontrepl(struct termp *p, enum termfont f)
374: {
375:
376: p->fontl = p->fontq[p->fonti];
377: p->fontq[p->fonti] = f;
1.1 kristaps 378: }
379:
380:
1.20 schwarze 381: void
382: term_fontpush(struct termp *p, enum termfont f)
1.1 kristaps 383: {
1.7 schwarze 384:
1.20 schwarze 385: assert(p->fonti + 1 < 10);
386: p->fontl = p->fontq[p->fonti];
387: p->fontq[++p->fonti] = f;
388: }
1.1 kristaps 389:
390:
1.20 schwarze 391: const void *
392: term_fontq(struct termp *p)
393: {
1.1 kristaps 394:
1.20 schwarze 395: return(&p->fontq[p->fonti]);
396: }
1.1 kristaps 397:
398:
1.20 schwarze 399: enum termfont
400: term_fonttop(struct termp *p)
401: {
1.1 kristaps 402:
1.20 schwarze 403: return(p->fontq[p->fonti]);
404: }
1.7 schwarze 405:
406:
1.20 schwarze 407: void
408: term_fontpopq(struct termp *p, const void *key)
409: {
1.1 kristaps 410:
1.20 schwarze 411: while (p->fonti >= 0 && key != &p->fontq[p->fonti])
412: p->fonti--;
413: assert(p->fonti >= 0);
414: }
1.1 kristaps 415:
416:
1.20 schwarze 417: void
418: term_fontpop(struct termp *p)
419: {
1.1 kristaps 420:
1.20 schwarze 421: assert(p->fonti);
422: p->fonti--;
1.1 kristaps 423: }
424:
425:
426: /*
427: * Handle pwords, partial words, which may be either a single word or a
428: * phrase that cannot be broken down (such as a literal string). This
429: * handles word styling.
430: */
1.7 schwarze 431: void
432: term_word(struct termp *p, const char *word)
1.1 kristaps 433: {
1.20 schwarze 434: const char *sv, *seq;
435: int sz;
436: size_t ssz;
437: enum roffdeco deco;
1.1 kristaps 438:
1.14 schwarze 439: sv = word;
440:
1.20 schwarze 441: if (word[0] && '\0' == word[1])
1.14 schwarze 442: switch (word[0]) {
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: /* FALLTHROUGH */
459: case('}'):
460: if ( ! (TERMP_IGNDELIM & p->flags))
461: p->flags |= TERMP_NOSPACE;
462: break;
463: default:
464: break;
465: }
1.1 kristaps 466:
467: if ( ! (TERMP_NOSPACE & p->flags))
1.20 schwarze 468: bufferc(p, ' ');
1.1 kristaps 469:
470: if ( ! (p->flags & TERMP_NONOSPACE))
471: p->flags &= ~TERMP_NOSPACE;
472:
1.20 schwarze 473: /* FIXME: use strcspn. */
474:
475: while (*word) {
476: if ('\\' != *word) {
477: encode(p, word, 1);
478: word++;
479: continue;
480: }
481:
482: seq = ++word;
483: sz = a2roffdeco(&deco, &seq, &ssz);
484:
485: switch (deco) {
486: case (DECO_RESERVED):
487: res(p, seq, ssz);
488: break;
489: case (DECO_SPECIAL):
490: spec(p, seq, ssz);
491: break;
492: case (DECO_BOLD):
493: term_fontrepl(p, TERMFONT_BOLD);
494: break;
495: case (DECO_ITALIC):
496: term_fontrepl(p, TERMFONT_UNDER);
497: break;
498: case (DECO_ROMAN):
499: term_fontrepl(p, TERMFONT_NONE);
500: break;
501: case (DECO_PREVIOUS):
502: term_fontlast(p);
503: break;
504: default:
505: break;
506: }
507:
508: word += sz;
509: if (DECO_NOSPACE == deco && '\0' == *word)
510: p->flags |= TERMP_NOSPACE;
511: }
1.1 kristaps 512:
1.14 schwarze 513: if (sv[0] && 0 == sv[1])
514: switch (sv[0]) {
515: case('('):
516: /* FALLTHROUGH */
517: case('['):
518: /* FALLTHROUGH */
519: case('{'):
520: p->flags |= TERMP_NOSPACE;
521: break;
522: default:
523: break;
524: }
1.1 kristaps 525: }
526:
527:
528: static void
1.20 schwarze 529: adjbuf(struct termp *p, size_t sz)
1.1 kristaps 530: {
531:
1.20 schwarze 532: if (0 == p->maxcols)
533: p->maxcols = 1024;
534: while (sz >= p->maxcols)
535: p->maxcols <<= 2;
536:
537: p->buf = realloc(p->buf, p->maxcols);
538: if (NULL == p->buf) {
539: perror(NULL);
540: exit(EXIT_FAILURE);
1.1 kristaps 541: }
542: }
543:
1.4 schwarze 544:
545: static void
1.20 schwarze 546: buffera(struct termp *p, const char *word, size_t sz)
547: {
548:
549: if (p->col + sz >= p->maxcols)
550: adjbuf(p, p->col + sz);
551:
552: memcpy(&p->buf[(int)p->col], word, sz);
553: p->col += sz;
554: }
555:
556:
557: static void
558: bufferc(struct termp *p, char c)
559: {
560:
561: if (p->col + 1 >= p->maxcols)
562: adjbuf(p, p->col + 1);
563:
564: p->buf[(int)p->col++] = c;
565: }
566:
567:
568: static void
569: encode(struct termp *p, const char *word, size_t sz)
1.4 schwarze 570: {
1.20 schwarze 571: enum termfont f;
572: int i;
573:
574: /*
575: * Encode and buffer a string of characters. If the current
576: * font mode is unset, buffer directly, else encode then buffer
577: * character by character.
578: */
579:
580: if (TERMFONT_NONE == (f = term_fonttop(p))) {
581: buffera(p, word, sz);
582: return;
583: }
584:
585: for (i = 0; i < (int)sz; i++) {
586: if ( ! isgraph((u_char)word[i])) {
587: bufferc(p, word[i]);
588: continue;
1.4 schwarze 589: }
1.20 schwarze 590:
591: if (TERMFONT_UNDER == f)
592: bufferc(p, '_');
593: else
594: bufferc(p, word[i]);
595:
596: bufferc(p, 8);
597: bufferc(p, word[i]);
1.4 schwarze 598: }
599: }
1.16 schwarze 600:
601:
602: size_t
603: term_vspan(const struct roffsu *su)
604: {
605: double r;
606:
607: switch (su->unit) {
608: case (SCALE_CM):
609: r = su->scale * 2;
610: break;
611: case (SCALE_IN):
612: r = su->scale * 6;
613: break;
614: case (SCALE_PC):
615: r = su->scale;
616: break;
617: case (SCALE_PT):
618: r = su->scale / 8;
619: break;
620: case (SCALE_MM):
621: r = su->scale / 1000;
622: break;
623: case (SCALE_VS):
624: r = su->scale;
625: break;
626: default:
627: r = su->scale - 1;
628: break;
629: }
630:
631: if (r < 0.0)
632: r = 0.0;
633: return(/* LINTED */(size_t)
634: r);
635: }
636:
637:
638: size_t
639: term_hspan(const struct roffsu *su)
640: {
641: double r;
642:
643: /* XXX: CM, IN, and PT are approximations. */
644:
645: switch (su->unit) {
646: case (SCALE_CM):
647: r = 4 * su->scale;
648: break;
649: case (SCALE_IN):
650: /* XXX: this is an approximation. */
651: r = 10 * su->scale;
652: break;
653: case (SCALE_PC):
654: r = (10 * su->scale) / 6;
655: break;
656: case (SCALE_PT):
657: r = (10 * su->scale) / 72;
658: break;
659: case (SCALE_MM):
660: r = su->scale / 1000; /* FIXME: double-check. */
661: break;
662: case (SCALE_VS):
663: r = su->scale * 2 - 1; /* FIXME: double-check. */
664: break;
665: default:
666: r = su->scale;
667: break;
668: }
669:
670: if (r < 0.0)
671: r = 0.0;
672: return((size_t)/* LINTED */
673: r);
674: }
675:
676: