]> git.cameronkatri.com Git - mandoc.git/blob - term.c
Added default print of `~' with empty `Pa' (not documented with OpenBSD, but still...
[mandoc.git] / term.c
1 /* $Id: term.c,v 1.91 2009/07/21 13:34:13 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_TWOSPACE: when padding, make sure there are at least two
188 * space characters of padding. Otherwise, rather break the line.
189 *
190 * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and
191 * the line is overrun, and don't pad-right if it's underrun.
192 *
193 * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when
194 * overruning, instead save the position and continue at that point
195 * when the next invocation.
196 *
197 * In-line line breaking:
198 *
199 * If TERMP_NOBREAK is specified and the line overruns the right
200 * margin, it will break and pad-right to the right margin after
201 * writing. If maxrmargin is violated, it will break and continue
202 * writing from the right-margin, which will lead to the above
203 * scenario upon exit.
204 *
205 * Otherwise, the line will break at the right margin. Extremely long
206 * lines will cause the system to emit a warning (TODO: hyphenate, if
207 * possible).
208 *
209 * FIXME: newline breaks occur (in groff) also occur when a single
210 * space follows a NOBREAK (try `Bl -tag')
211 *
212 * FIXME: there's a newline error where a `Bl -diag' will have a
213 * trailing newline if the line is exactly 73 chars long.
214 */
215 void
216 term_flushln(struct termp *p)
217 {
218 int i, j;
219 size_t vbl, vsz, vis, maxvis, mmax, bp;
220 static int overstep = 0;
221
222 /*
223 * First, establish the maximum columns of "visible" content.
224 * This is usually the difference between the right-margin and
225 * an indentation, but can be, for tagged lists or columns, a
226 * small set of values.
227 */
228
229 assert(p->offset < p->rmargin);
230 maxvis = p->rmargin - p->offset - overstep;
231 mmax = p->maxrmargin - p->offset - overstep;
232 bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
233 vis = 0;
234 overstep = 0;
235
236 /*
237 * If in the standard case (left-justified), then begin with our
238 * indentation, otherwise (columns, etc.) just start spitting
239 * out text.
240 */
241
242 if ( ! (p->flags & TERMP_NOLPAD))
243 /* LINTED */
244 for (j = 0; j < (int)p->offset; j++)
245 putchar(' ');
246
247 for (i = 0; i < (int)p->col; i++) {
248 /*
249 * Count up visible word characters. Control sequences
250 * (starting with the CSI) aren't counted. A space
251 * generates a non-printing word, which is valid (the
252 * space is printed according to regular spacing rules).
253 */
254
255 /* LINTED */
256 for (j = i, vsz = 0; j < (int)p->col; j++) {
257 if (' ' == p->buf[j])
258 break;
259 else if (8 == p->buf[j])
260 vsz--;
261 else
262 vsz++;
263 }
264
265 /*
266 * Choose the number of blanks to prepend: no blank at the
267 * beginning of a line, one between words -- but do not
268 * actually write them yet.
269 */
270 vbl = (size_t)(0 == vis ? 0 : 1);
271
272 /*
273 * Find out whether we would exceed the right margin.
274 * If so, break to the next line. (TODO: hyphenate)
275 * Otherwise, write the chosen number of blanks now.
276 */
277 if (vis && vis + vbl + vsz > bp) {
278 putchar('\n');
279 if (TERMP_NOBREAK & p->flags) {
280 for (j = 0; j < (int)p->rmargin; j++)
281 putchar(' ');
282 vis = p->rmargin - p->offset;
283 } else {
284 for (j = 0; j < (int)p->offset; j++)
285 putchar(' ');
286 vis = 0;
287 }
288 } else {
289 for (j = 0; j < (int)vbl; j++)
290 putchar(' ');
291 vis += vbl;
292 }
293
294 /*
295 * Finally, write out the word.
296 */
297 for ( ; i < (int)p->col; i++) {
298 if (' ' == p->buf[i])
299 break;
300 putchar(p->buf[i]);
301 }
302 vis += vsz;
303 }
304 p->col = 0;
305
306 if ( ! (TERMP_NOBREAK & p->flags)) {
307 putchar('\n');
308 return;
309 }
310
311 if (TERMP_HANG & p->flags) {
312
313 /* We need one blank after the tag. */
314 overstep = vis - maxvis + 1;
315
316 /*
317 * Behave exactly the same way as groff:
318 * If we have overstepped the margin, temporarily move it
319 * to the right and flag the rest of the line to be shorter.
320 * If we landed right at the margin, be happy.
321 * If we are one step before the margin, temporarily move it
322 * one step LEFT and flag the rest of the line to be longer.
323 */
324 if (overstep >= -1)
325 maxvis += overstep;
326 else
327 overstep = 0;
328
329 } else if (TERMP_DANGLE & p->flags)
330 return;
331
332 if (maxvis > vis + ((TERMP_TWOSPACE & p->flags) ? 1 : 0)) /* pad */
333 for ( ; vis < maxvis; vis++)
334 putchar(' ');
335 else { /* break */
336 putchar('\n');
337 for (i = 0; i < (int)p->rmargin; i++)
338 putchar(' ');
339 }
340 }
341
342
343 /*
344 * A newline only breaks an existing line; it won't assert vertical
345 * space. All data in the output buffer is flushed prior to the newline
346 * assertion.
347 */
348 void
349 term_newln(struct termp *p)
350 {
351
352 p->flags |= TERMP_NOSPACE;
353 if (0 == p->col) {
354 p->flags &= ~TERMP_NOLPAD;
355 return;
356 }
357 term_flushln(p);
358 p->flags &= ~TERMP_NOLPAD;
359 }
360
361
362 /*
363 * Asserts a vertical space (a full, empty line-break between lines).
364 * Note that if used twice, this will cause two blank spaces and so on.
365 * All data in the output buffer is flushed prior to the newline
366 * assertion.
367 */
368 void
369 term_vspace(struct termp *p)
370 {
371
372 term_newln(p);
373 putchar('\n');
374 }
375
376
377 /*
378 * Determine the symbol indicated by an escape sequences, that is, one
379 * starting with a backslash. Once done, we pass this value into the
380 * output buffer by way of the symbol table.
381 */
382 static void
383 term_nescape(struct termp *p, const char *word, size_t len)
384 {
385 const char *rhs;
386 size_t sz;
387 int i;
388
389 rhs = term_a2ascii(p->symtab, word, len, &sz);
390
391 if (rhs)
392 for (i = 0; i < (int)sz; i++)
393 term_encodea(p, rhs[i]);
394 }
395
396
397 /*
398 * Handle an escape sequence: determine its length and pass it to the
399 * escape-symbol look table. Note that we assume mdoc(3) has validated
400 * the escape sequence (we assert upon badly-formed escape sequences).
401 */
402 static void
403 term_pescape(struct termp *p, const char **word)
404 {
405 int j;
406 const char *wp;
407
408 wp = *word;
409
410 if (0 == *(++wp)) {
411 *word = wp;
412 return;
413 }
414
415 if ('(' == *wp) {
416 wp++;
417 if (0 == *wp || 0 == *(wp + 1)) {
418 *word = 0 == *wp ? wp : wp + 1;
419 return;
420 }
421
422 term_nescape(p, wp, 2);
423 *word = ++wp;
424 return;
425
426 } else if ('*' == *wp) {
427 if (0 == *(++wp)) {
428 *word = wp;
429 return;
430 }
431
432 switch (*wp) {
433 case ('('):
434 wp++;
435 if (0 == *wp || 0 == *(wp + 1)) {
436 *word = 0 == *wp ? wp : wp + 1;
437 return;
438 }
439
440 term_nescape(p, wp, 2);
441 *word = ++wp;
442 return;
443 case ('['):
444 break;
445 default:
446 term_nescape(p, wp, 1);
447 *word = wp;
448 return;
449 }
450
451 } else if ('f' == *wp) {
452 if (0 == *(++wp)) {
453 *word = wp;
454 return;
455 }
456
457 switch (*wp) {
458 case ('B'):
459 p->flags |= TERMP_BOLD;
460 break;
461 case ('I'):
462 p->flags |= TERMP_UNDER;
463 break;
464 case ('P'):
465 /* FALLTHROUGH */
466 case ('R'):
467 p->flags &= ~TERMP_STYLE;
468 break;
469 default:
470 break;
471 }
472
473 *word = wp;
474 return;
475
476 } else if ('[' != *wp) {
477 term_nescape(p, wp, 1);
478 *word = wp;
479 return;
480 }
481
482 wp++;
483 for (j = 0; *wp && ']' != *wp; wp++, j++)
484 /* Loop... */ ;
485
486 if (0 == *wp) {
487 *word = wp;
488 return;
489 }
490
491 term_nescape(p, wp - j, (size_t)j);
492 *word = wp;
493 }
494
495
496 /*
497 * Handle pwords, partial words, which may be either a single word or a
498 * phrase that cannot be broken down (such as a literal string). This
499 * handles word styling.
500 */
501 void
502 term_word(struct termp *p, const char *word)
503 {
504 const char *sv;
505
506 if (term_isclosedelim(word))
507 if ( ! (TERMP_IGNDELIM & p->flags))
508 p->flags |= TERMP_NOSPACE;
509
510 if ( ! (TERMP_NOSPACE & p->flags))
511 term_chara(p, ' ');
512
513 if ( ! (p->flags & TERMP_NONOSPACE))
514 p->flags &= ~TERMP_NOSPACE;
515
516 /*
517 * If ANSI (word-length styling), then apply our style now,
518 * before the word.
519 */
520
521 for (sv = word; *word; word++)
522 if ('\\' != *word)
523 term_encodea(p, *word);
524 else
525 term_pescape(p, &word);
526
527 if (term_isopendelim(sv))
528 p->flags |= TERMP_NOSPACE;
529 }
530
531
532 /*
533 * Insert a single character into the line-buffer. If the buffer's
534 * space is exceeded, then allocate more space by doubling the buffer
535 * size.
536 */
537 static void
538 term_chara(struct termp *p, char c)
539 {
540 size_t s;
541
542 if (p->col + 1 >= p->maxcols) {
543 if (0 == p->maxcols)
544 p->maxcols = 256;
545 s = p->maxcols * 2;
546 p->buf = realloc(p->buf, s);
547 if (NULL == p->buf)
548 err(1, "realloc");
549 p->maxcols = s;
550 }
551 p->buf[(int)(p->col)++] = c;
552 }
553
554
555 static void
556 term_encodea(struct termp *p, char c)
557 {
558
559 if (' ' != c && TERMP_STYLE & p->flags) {
560 if (TERMP_BOLD & p->flags) {
561 term_chara(p, c);
562 term_chara(p, 8);
563 }
564 if (TERMP_UNDER & p->flags) {
565 term_chara(p, '_');
566 term_chara(p, 8);
567 }
568 }
569 term_chara(p, c);
570 }