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