]> git.cameronkatri.com Git - mandoc.git/blob - term.c
6bfd373d64276f57c079287b13e984538a39f09e
[mandoc.git] / term.c
1 /* $Id: term.c,v 1.100 2009/09/16 15:08:31 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
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.
16 */
17 #include <assert.h>
18 #include <err.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include "term.h"
24 #include "man.h"
25 #include "mdoc.h"
26
27 extern void man_run(struct termp *,
28 const struct man *);
29 extern void mdoc_run(struct termp *,
30 const struct mdoc *);
31
32 static struct termp *term_alloc(enum termenc);
33 static void term_free(struct termp *);
34
35 static void do_escaped(struct termp *, const char **);
36 static void do_special(struct termp *,
37 const char *, size_t);
38 static void do_reserved(struct termp *,
39 const char *, size_t);
40 static void buffer(struct termp *, char);
41 static void encode(struct termp *, char);
42
43
44 void *
45 ascii_alloc(void)
46 {
47
48 return(term_alloc(TERMENC_ASCII));
49 }
50
51
52 void
53 terminal_man(void *arg, const struct man *man)
54 {
55 struct termp *p;
56
57 p = (struct termp *)arg;
58 if (NULL == p->symtab)
59 p->symtab = term_ascii2htab();
60
61 man_run(p, man);
62 }
63
64
65 void
66 terminal_mdoc(void *arg, const struct mdoc *mdoc)
67 {
68 struct termp *p;
69
70 p = (struct termp *)arg;
71 if (NULL == p->symtab)
72 p->symtab = term_ascii2htab();
73
74 mdoc_run(p, mdoc);
75 }
76
77
78 void
79 terminal_free(void *arg)
80 {
81
82 term_free((struct termp *)arg);
83 }
84
85
86 static void
87 term_free(struct termp *p)
88 {
89
90 if (p->buf)
91 free(p->buf);
92 if (TERMENC_ASCII == p->enc && p->symtab)
93 term_asciifree(p->symtab);
94
95 free(p);
96 }
97
98
99 static struct termp *
100 term_alloc(enum termenc enc)
101 {
102 struct termp *p;
103
104 if (NULL == (p = malloc(sizeof(struct termp))))
105 return(NULL);
106 bzero(p, sizeof(struct termp));
107 p->maxrmargin = 78;
108 p->enc = enc;
109 return(p);
110 }
111
112
113 /*
114 * Flush a line of text. A "line" is loosely defined as being something
115 * that should be followed by a newline, regardless of whether it's
116 * broken apart by newlines getting there. A line can also be a
117 * fragment of a columnar list.
118 *
119 * Specifically, a line is whatever's in p->buf of length p->col, which
120 * is zeroed after this function returns.
121 *
122 * The usage of termp:flags is as follows:
123 *
124 * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
125 * offset value. This is useful when doing columnar lists where the
126 * prior column has right-padded.
127 *
128 * - TERMP_NOBREAK: this is the most important and is used when making
129 * columns. In short: don't print a newline and instead pad to the
130 * right margin. Used in conjunction with TERMP_NOLPAD.
131 *
132 * - TERMP_TWOSPACE: when padding, make sure there are at least two
133 * space characters of padding. Otherwise, rather break the line.
134 *
135 * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and
136 * the line is overrun, and don't pad-right if it's underrun.
137 *
138 * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when
139 * overruning, instead save the position and continue at that point
140 * when the next invocation.
141 *
142 * In-line line breaking:
143 *
144 * If TERMP_NOBREAK is specified and the line overruns the right
145 * margin, it will break and pad-right to the right margin after
146 * writing. If maxrmargin is violated, it will break and continue
147 * writing from the right-margin, which will lead to the above
148 * scenario upon exit.
149 *
150 * Otherwise, the line will break at the right margin. Extremely long
151 * lines will cause the system to emit a warning (TODO: hyphenate, if
152 * possible).
153 */
154 void
155 term_flushln(struct termp *p)
156 {
157 int i, j;
158 size_t vbl, vsz, vis, maxvis, mmax, bp;
159 static int overstep = 0;
160
161 /*
162 * First, establish the maximum columns of "visible" content.
163 * This is usually the difference between the right-margin and
164 * an indentation, but can be, for tagged lists or columns, a
165 * small set of values.
166 */
167
168 assert(p->offset < p->rmargin);
169 assert((int)(p->rmargin - p->offset) - overstep > 0);
170
171 maxvis = /* LINTED */
172 p->rmargin - p->offset - overstep;
173 mmax = /* LINTED */
174 p->maxrmargin - p->offset - overstep;
175
176 bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
177 vis = 0;
178 overstep = 0;
179
180 /*
181 * If in the standard case (left-justified), then begin with our
182 * indentation, otherwise (columns, etc.) just start spitting
183 * out text.
184 */
185
186 if ( ! (p->flags & TERMP_NOLPAD))
187 /* LINTED */
188 for (j = 0; j < (int)p->offset; j++)
189 putchar(' ');
190
191 for (i = 0; i < (int)p->col; i++) {
192 /*
193 * Count up visible word characters. Control sequences
194 * (starting with the CSI) aren't counted. A space
195 * generates a non-printing word, which is valid (the
196 * space is printed according to regular spacing rules).
197 */
198
199 /* LINTED */
200 for (j = i, vsz = 0; j < (int)p->col; j++) {
201 if (j && ' ' == p->buf[j])
202 break;
203 else if (8 == p->buf[j])
204 vsz--;
205 else
206 vsz++;
207 }
208
209 /*
210 * Choose the number of blanks to prepend: no blank at the
211 * beginning of a line, one between words -- but do not
212 * actually write them yet.
213 */
214 vbl = (size_t)(0 == vis ? 0 : 1);
215
216 /*
217 * Find out whether we would exceed the right margin.
218 * If so, break to the next line. (TODO: hyphenate)
219 * Otherwise, write the chosen number of blanks now.
220 */
221 if (vis && vis + vbl + vsz > bp) {
222 putchar('\n');
223 if (TERMP_NOBREAK & p->flags) {
224 for (j = 0; j < (int)p->rmargin; j++)
225 putchar(' ');
226 vis = p->rmargin - p->offset;
227 } else {
228 for (j = 0; j < (int)p->offset; j++)
229 putchar(' ');
230 vis = 0;
231 }
232 } else {
233 for (j = 0; j < (int)vbl; j++)
234 putchar(' ');
235 vis += vbl;
236 }
237
238 /*
239 * Finally, write out the word.
240 */
241 for ( ; i < (int)p->col; i++) {
242 if (' ' == p->buf[i])
243 break;
244 putchar(p->buf[i]);
245 }
246 vis += vsz;
247 }
248 p->col = 0;
249
250 if ( ! (TERMP_NOBREAK & p->flags)) {
251 putchar('\n');
252 return;
253 }
254
255 if (TERMP_HANG & p->flags) {
256 /* We need one blank after the tag. */
257 overstep = /* LINTED */
258 vis - maxvis + 1;
259
260 /*
261 * Behave exactly the same way as groff:
262 * If we have overstepped the margin, temporarily move
263 * it to the right and flag the rest of the line to be
264 * shorter.
265 * If we landed right at the margin, be happy.
266 * If we are one step before the margin, temporarily
267 * move it one step LEFT and flag the rest of the line
268 * to be longer.
269 */
270 if (overstep >= -1) {
271 assert((int)maxvis + overstep >= 0);
272 /* LINTED */
273 maxvis += overstep;
274 } else
275 overstep = 0;
276
277 } else if (TERMP_DANGLE & p->flags)
278 return;
279
280 /* Right-pad. */
281 if (maxvis > vis + /* LINTED */
282 ((TERMP_TWOSPACE & p->flags) ? 1 : 0))
283 for ( ; vis < maxvis; vis++)
284 putchar(' ');
285 else { /* ...or newline break. */
286 putchar('\n');
287 for (i = 0; i < (int)p->rmargin; i++)
288 putchar(' ');
289 }
290 }
291
292
293 /*
294 * A newline only breaks an existing line; it won't assert vertical
295 * space. All data in the output buffer is flushed prior to the newline
296 * assertion.
297 */
298 void
299 term_newln(struct termp *p)
300 {
301
302 p->flags |= TERMP_NOSPACE;
303 if (0 == p->col) {
304 p->flags &= ~TERMP_NOLPAD;
305 return;
306 }
307 term_flushln(p);
308 p->flags &= ~TERMP_NOLPAD;
309 }
310
311
312 /*
313 * Asserts a vertical space (a full, empty line-break between lines).
314 * Note that if used twice, this will cause two blank spaces and so on.
315 * All data in the output buffer is flushed prior to the newline
316 * assertion.
317 */
318 void
319 term_vspace(struct termp *p)
320 {
321
322 term_newln(p);
323 putchar('\n');
324 }
325
326
327 static void
328 do_special(struct termp *p, const char *word, size_t len)
329 {
330 const char *rhs;
331 size_t sz;
332 int i;
333
334 rhs = term_a2ascii(p->symtab, word, len, &sz);
335
336 if (NULL == rhs) {
337 #if 0
338 fputs("Unknown special character: ", stderr);
339 for (i = 0; i < (int)len; i++)
340 fputc(word[i], stderr);
341 fputc('\n', stderr);
342 #endif
343 return;
344 }
345 for (i = 0; i < (int)sz; i++)
346 encode(p, rhs[i]);
347 }
348
349
350 static void
351 do_reserved(struct termp *p, const char *word, size_t len)
352 {
353 const char *rhs;
354 size_t sz;
355 int i;
356
357 rhs = term_a2res(p->symtab, word, len, &sz);
358
359 if (NULL == rhs) {
360 #if 0
361 fputs("Unknown reserved word: ", stderr);
362 for (i = 0; i < (int)len; i++)
363 fputc(word[i], stderr);
364 fputc('\n', stderr);
365 #endif
366 return;
367 }
368 for (i = 0; i < (int)sz; i++)
369 encode(p, rhs[i]);
370 }
371
372
373 /*
374 * Handle an escape sequence: determine its length and pass it to the
375 * escape-symbol look table. Note that we assume mdoc(3) has validated
376 * the escape sequence (we assert upon badly-formed escape sequences).
377 */
378 static void
379 do_escaped(struct termp *p, const char **word)
380 {
381 int j, type;
382 const char *wp;
383
384 wp = *word;
385 type = 1;
386
387 if (0 == *(++wp)) {
388 *word = wp;
389 return;
390 }
391
392 if ('(' == *wp) {
393 wp++;
394 if (0 == *wp || 0 == *(wp + 1)) {
395 *word = 0 == *wp ? wp : wp + 1;
396 return;
397 }
398
399 do_special(p, wp, 2);
400 *word = ++wp;
401 return;
402
403 } else if ('*' == *wp) {
404 if (0 == *(++wp)) {
405 *word = wp;
406 return;
407 }
408
409 switch (*wp) {
410 case ('('):
411 wp++;
412 if (0 == *wp || 0 == *(wp + 1)) {
413 *word = 0 == *wp ? wp : wp + 1;
414 return;
415 }
416
417 do_reserved(p, wp, 2);
418 *word = ++wp;
419 return;
420 case ('['):
421 type = 0;
422 break;
423 default:
424 do_reserved(p, wp, 1);
425 *word = wp;
426 return;
427 }
428
429 } else if ('f' == *wp) {
430 if (0 == *(++wp)) {
431 *word = wp;
432 return;
433 }
434
435 switch (*wp) {
436 case ('B'):
437 p->bold++;
438 break;
439 case ('I'):
440 p->under++;
441 break;
442 case ('P'):
443 /* FALLTHROUGH */
444 case ('R'):
445 p->bold = p->under = 0;
446 break;
447 default:
448 break;
449 }
450
451 *word = wp;
452 return;
453
454 } else if ('[' != *wp) {
455 do_special(p, wp, 1);
456 *word = wp;
457 return;
458 }
459
460 wp++;
461 for (j = 0; *wp && ']' != *wp; wp++, j++)
462 /* Loop... */ ;
463
464 if (0 == *wp) {
465 *word = wp;
466 return;
467 }
468
469 if (type)
470 do_special(p, wp - j, (size_t)j);
471 else
472 do_reserved(p, wp - j, (size_t)j);
473 *word = wp;
474 }
475
476
477 /*
478 * Handle pwords, partial words, which may be either a single word or a
479 * phrase that cannot be broken down (such as a literal string). This
480 * handles word styling.
481 */
482 void
483 term_word(struct termp *p, const char *word)
484 {
485 const char *sv;
486
487 sv = word;
488
489 if (word[0] && 0 == word[1])
490 switch (word[0]) {
491 case('.'):
492 /* FALLTHROUGH */
493 case(','):
494 /* FALLTHROUGH */
495 case(';'):
496 /* FALLTHROUGH */
497 case(':'):
498 /* FALLTHROUGH */
499 case('?'):
500 /* FALLTHROUGH */
501 case('!'):
502 /* FALLTHROUGH */
503 case(')'):
504 /* FALLTHROUGH */
505 case(']'):
506 /* FALLTHROUGH */
507 case('}'):
508 if ( ! (TERMP_IGNDELIM & p->flags))
509 p->flags |= TERMP_NOSPACE;
510 break;
511 default:
512 break;
513 }
514
515 if ( ! (TERMP_NOSPACE & p->flags))
516 buffer(p, ' ');
517
518 if ( ! (p->flags & TERMP_NONOSPACE))
519 p->flags &= ~TERMP_NOSPACE;
520
521 for ( ; *word; word++)
522 if ('\\' != *word)
523 encode(p, *word);
524 else
525 do_escaped(p, &word);
526
527 if (sv[0] && 0 == sv[1])
528 switch (sv[0]) {
529 case('('):
530 /* FALLTHROUGH */
531 case('['):
532 /* FALLTHROUGH */
533 case('{'):
534 p->flags |= TERMP_NOSPACE;
535 break;
536 default:
537 break;
538 }
539 }
540
541
542 /*
543 * Insert a single character into the line-buffer. If the buffer's
544 * space is exceeded, then allocate more space by doubling the buffer
545 * size.
546 */
547 static void
548 buffer(struct termp *p, char c)
549 {
550 size_t s;
551
552 if (p->col + 1 >= p->maxcols) {
553 if (0 == p->maxcols)
554 p->maxcols = 256;
555 s = p->maxcols * 2;
556 p->buf = realloc(p->buf, s);
557 if (NULL == p->buf)
558 err(1, "realloc"); /* FIXME: shouldn't be here! */
559 p->maxcols = s;
560 }
561 p->buf[(int)(p->col)++] = c;
562 }
563
564
565 static void
566 encode(struct termp *p, char c)
567 {
568
569 if (' ' != c) {
570 if (p->bold) {
571 buffer(p, c);
572 buffer(p, 8);
573 }
574 if (p->under) {
575 buffer(p, '_');
576 buffer(p, 8);
577 }
578 }
579 buffer(p, c);
580 }