]> git.cameronkatri.com Git - mandoc.git/blob - term.c
Portability: replaced queue macros in html.c (Joerg Sonnenberger).
[mandoc.git] / term.c
1 /* $Id: term.c,v 1.111 2009/10/26 07:18:23 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 "chars.h"
24 #include "out.h"
25 #include "term.h"
26 #include "man.h"
27 #include "mdoc.h"
28 #include "main.h"
29
30 /* FIXME: accomodate non-breaking, non-collapsing white-space. */
31 /* FIXME: accomodate non-breaking, collapsing white-space. */
32
33 static struct termp *term_alloc(enum termenc);
34 static void term_free(struct termp *);
35
36 static void do_escaped(struct termp *, const char **);
37 static void do_special(struct termp *,
38 const char *, size_t);
39 static void do_reserved(struct termp *,
40 const char *, size_t);
41 static void buffer(struct termp *, char);
42 static void encode(struct termp *, char);
43
44
45 void *
46 ascii_alloc(void)
47 {
48
49 return(term_alloc(TERMENC_ASCII));
50 }
51
52
53 void
54 terminal_free(void *arg)
55 {
56
57 term_free((struct termp *)arg);
58 }
59
60
61 static void
62 term_free(struct termp *p)
63 {
64
65 if (p->buf)
66 free(p->buf);
67 if (p->symtab)
68 chars_free(p->symtab);
69
70 free(p);
71 }
72
73
74 static struct termp *
75 term_alloc(enum termenc enc)
76 {
77 struct termp *p;
78
79 if (NULL == (p = malloc(sizeof(struct termp))))
80 return(NULL);
81 bzero(p, sizeof(struct termp));
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 *
97 * The usage of termp:flags is as follows:
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 *
107 * - TERMP_TWOSPACE: when padding, make sure there are at least two
108 * space characters of padding. Otherwise, rather break the line.
109 *
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.
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
122 * writing from the right-margin, which will lead to the above
123 * scenario upon exit.
124 *
125 * Otherwise, the line will break at the right margin. Extremely long
126 * lines will cause the system to emit a warning (TODO: hyphenate, if
127 * possible).
128 */
129 void
130 term_flushln(struct termp *p)
131 {
132 int i, j;
133 size_t vbl, vsz, vis, maxvis, mmax, bp;
134 static int overstep = 0;
135
136 /*
137 * First, establish the maximum columns of "visible" content.
138 * This is usually the difference between the right-margin and
139 * an indentation, but can be, for tagged lists or columns, a
140 * small set of values.
141 */
142
143 assert(p->offset < p->rmargin);
144 assert((int)(p->rmargin - p->offset) - overstep > 0);
145
146 maxvis = /* LINTED */
147 p->rmargin - p->offset - overstep;
148 mmax = /* LINTED */
149 p->maxrmargin - p->offset - overstep;
150
151 bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
152 vis = 0;
153
154 /*
155 * If in the standard case (left-justified), then begin with our
156 * indentation, otherwise (columns, etc.) just start spitting
157 * out text.
158 */
159
160 if ( ! (p->flags & TERMP_NOLPAD))
161 /* LINTED */
162 for (j = 0; j < (int)p->offset; j++)
163 putchar(' ');
164
165 for (i = 0; i < (int)p->col; i++) {
166 /*
167 * Count up visible word characters. Control sequences
168 * (starting with the CSI) aren't counted. A space
169 * generates a non-printing word, which is valid (the
170 * space is printed according to regular spacing rules).
171 */
172
173 /* LINTED */
174 for (j = i, vsz = 0; j < (int)p->col; j++) {
175 if (j && ' ' == p->buf[j])
176 break;
177 else if (8 == p->buf[j])
178 vsz--;
179 else
180 vsz++;
181 }
182
183 /*
184 * Choose the number of blanks to prepend: no blank at the
185 * beginning of a line, one between words -- but do not
186 * actually write them yet.
187 */
188 vbl = (size_t)(0 == vis ? 0 : 1);
189
190 /*
191 * Find out whether we would exceed the right margin.
192 * If so, break to the next line. (TODO: hyphenate)
193 * Otherwise, write the chosen number of blanks now.
194 */
195 if (vis && vis + vbl + vsz > bp) {
196 putchar('\n');
197 if (TERMP_NOBREAK & p->flags) {
198 for (j = 0; j < (int)p->rmargin; j++)
199 putchar(' ');
200 vis = p->rmargin - p->offset;
201 } else {
202 for (j = 0; j < (int)p->offset; j++)
203 putchar(' ');
204 vis = 0;
205 }
206 /* Remove the overstep width. */
207 bp += overstep;
208 overstep = 0;
209 } else {
210 for (j = 0; j < (int)vbl; j++)
211 putchar(' ');
212 vis += vbl;
213 }
214
215 /*
216 * Finally, write out the word.
217 */
218 for ( ; i < (int)p->col; i++) {
219 if (' ' == p->buf[i])
220 break;
221 putchar(p->buf[i]);
222 }
223 vis += vsz;
224 }
225
226 p->col = 0;
227 overstep = 0;
228
229 if ( ! (TERMP_NOBREAK & p->flags)) {
230 putchar('\n');
231 return;
232 }
233
234 if (TERMP_HANG & p->flags) {
235 /* We need one blank after the tag. */
236 overstep = /* LINTED */
237 vis - maxvis + 1;
238
239 /*
240 * Behave exactly the same way as groff:
241 * If we have overstepped the margin, temporarily move
242 * it to the right and flag the rest of the line to be
243 * shorter.
244 * If we landed right at the margin, be happy.
245 * If we are one step before the margin, temporarily
246 * move it one step LEFT and flag the rest of the line
247 * to be longer.
248 */
249 if (overstep >= -1) {
250 assert((int)maxvis + overstep >= 0);
251 /* LINTED */
252 maxvis += overstep;
253 } else
254 overstep = 0;
255
256 } else if (TERMP_DANGLE & p->flags)
257 return;
258
259 /* Right-pad. */
260 if (maxvis > vis + /* LINTED */
261 ((TERMP_TWOSPACE & p->flags) ? 1 : 0))
262 for ( ; vis < maxvis; vis++)
263 putchar(' ');
264 else { /* ...or newline break. */
265 putchar('\n');
266 for (i = 0; i < (int)p->rmargin; i++)
267 putchar(' ');
268 }
269 }
270
271
272 /*
273 * A newline only breaks an existing line; it won't assert vertical
274 * space. All data in the output buffer is flushed prior to the newline
275 * assertion.
276 */
277 void
278 term_newln(struct termp *p)
279 {
280
281 p->flags |= TERMP_NOSPACE;
282 if (0 == p->col) {
283 p->flags &= ~TERMP_NOLPAD;
284 return;
285 }
286 term_flushln(p);
287 p->flags &= ~TERMP_NOLPAD;
288 }
289
290
291 /*
292 * Asserts a vertical space (a full, empty line-break between lines).
293 * Note that if used twice, this will cause two blank spaces and so on.
294 * All data in the output buffer is flushed prior to the newline
295 * assertion.
296 */
297 void
298 term_vspace(struct termp *p)
299 {
300
301 term_newln(p);
302 putchar('\n');
303 }
304
305
306 static void
307 do_special(struct termp *p, const char *word, size_t len)
308 {
309 const char *rhs;
310 size_t sz;
311 int i;
312
313 rhs = chars_a2ascii(p->symtab, word, len, &sz);
314
315 if (NULL == rhs) {
316 #if 0
317 fputs("Unknown special character: ", stderr);
318 for (i = 0; i < (int)len; i++)
319 fputc(word[i], stderr);
320 fputc('\n', stderr);
321 #endif
322 return;
323 }
324 for (i = 0; i < (int)sz; i++)
325 encode(p, rhs[i]);
326 }
327
328
329 static void
330 do_reserved(struct termp *p, const char *word, size_t len)
331 {
332 const char *rhs;
333 size_t sz;
334 int i;
335
336 rhs = chars_a2res(p->symtab, word, len, &sz);
337
338 if (NULL == rhs) {
339 #if 0
340 fputs("Unknown reserved word: ", stderr);
341 for (i = 0; i < (int)len; i++)
342 fputc(word[i], stderr);
343 fputc('\n', stderr);
344 #endif
345 return;
346 }
347 for (i = 0; i < (int)sz; i++)
348 encode(p, rhs[i]);
349 }
350
351
352 /*
353 * Handle an escape sequence: determine its length and pass it to the
354 * escape-symbol look table. Note that we assume mdoc(3) has validated
355 * the escape sequence (we assert upon badly-formed escape sequences).
356 */
357 static void
358 do_escaped(struct termp *p, const char **word)
359 {
360 int j, type;
361 const char *wp;
362
363 wp = *word;
364 type = 1;
365
366 if (0 == *(++wp)) {
367 *word = wp;
368 return;
369 }
370
371 if ('(' == *wp) {
372 wp++;
373 if (0 == *wp || 0 == *(wp + 1)) {
374 *word = 0 == *wp ? wp : wp + 1;
375 return;
376 }
377
378 do_special(p, wp, 2);
379 *word = ++wp;
380 return;
381
382 } else if ('*' == *wp) {
383 if (0 == *(++wp)) {
384 *word = wp;
385 return;
386 }
387
388 switch (*wp) {
389 case ('('):
390 wp++;
391 if (0 == *wp || 0 == *(wp + 1)) {
392 *word = 0 == *wp ? wp : wp + 1;
393 return;
394 }
395
396 do_reserved(p, wp, 2);
397 *word = ++wp;
398 return;
399 case ('['):
400 type = 0;
401 break;
402 default:
403 do_reserved(p, wp, 1);
404 *word = wp;
405 return;
406 }
407
408 } else if ('f' == *wp) {
409 if (0 == *(++wp)) {
410 *word = wp;
411 return;
412 }
413
414 switch (*wp) {
415 case ('B'):
416 p->bold++;
417 break;
418 case ('I'):
419 p->under++;
420 break;
421 case ('P'):
422 /* FALLTHROUGH */
423 case ('R'):
424 p->bold = p->under = 0;
425 break;
426 default:
427 break;
428 }
429
430 *word = wp;
431 return;
432
433 } else if ('[' != *wp) {
434 do_special(p, wp, 1);
435 *word = wp;
436 return;
437 }
438
439 wp++;
440 for (j = 0; *wp && ']' != *wp; wp++, j++)
441 /* Loop... */ ;
442
443 if (0 == *wp) {
444 *word = wp;
445 return;
446 }
447
448 if (type)
449 do_special(p, wp - j, (size_t)j);
450 else
451 do_reserved(p, wp - j, (size_t)j);
452 *word = wp;
453 }
454
455
456 /*
457 * Handle pwords, partial words, which may be either a single word or a
458 * phrase that cannot be broken down (such as a literal string). This
459 * handles word styling.
460 */
461 void
462 term_word(struct termp *p, const char *word)
463 {
464 const char *sv;
465
466 sv = word;
467
468 if (word[0] && 0 == word[1])
469 switch (word[0]) {
470 case('.'):
471 /* FALLTHROUGH */
472 case(','):
473 /* FALLTHROUGH */
474 case(';'):
475 /* FALLTHROUGH */
476 case(':'):
477 /* FALLTHROUGH */
478 case('?'):
479 /* FALLTHROUGH */
480 case('!'):
481 /* FALLTHROUGH */
482 case(')'):
483 /* FALLTHROUGH */
484 case(']'):
485 /* FALLTHROUGH */
486 case('}'):
487 if ( ! (TERMP_IGNDELIM & p->flags))
488 p->flags |= TERMP_NOSPACE;
489 break;
490 default:
491 break;
492 }
493
494 if ( ! (TERMP_NOSPACE & p->flags))
495 buffer(p, ' ');
496
497 if ( ! (p->flags & TERMP_NONOSPACE))
498 p->flags &= ~TERMP_NOSPACE;
499
500 for ( ; *word; word++)
501 if ('\\' != *word)
502 encode(p, *word);
503 else
504 do_escaped(p, &word);
505
506 if (sv[0] && 0 == sv[1])
507 switch (sv[0]) {
508 case('('):
509 /* FALLTHROUGH */
510 case('['):
511 /* FALLTHROUGH */
512 case('{'):
513 p->flags |= TERMP_NOSPACE;
514 break;
515 default:
516 break;
517 }
518 }
519
520
521 /*
522 * Insert a single character into the line-buffer. If the buffer's
523 * space is exceeded, then allocate more space by doubling the buffer
524 * size.
525 */
526 static void
527 buffer(struct termp *p, char c)
528 {
529 size_t s;
530
531 if (p->col + 1 >= p->maxcols) {
532 if (0 == p->maxcols)
533 p->maxcols = 256;
534 s = p->maxcols * 2;
535 p->buf = realloc(p->buf, s);
536 if (NULL == p->buf)
537 err(1, "realloc"); /* FIXME: shouldn't be here! */
538 p->maxcols = s;
539 }
540 p->buf[(int)(p->col)++] = c;
541 }
542
543
544 static void
545 encode(struct termp *p, char c)
546 {
547
548 if (' ' != c) {
549 if (p->under) {
550 buffer(p, '_');
551 buffer(p, 8);
552 }
553 if (p->bold) {
554 buffer(p, c);
555 buffer(p, 8);
556 }
557 }
558 buffer(p, c);
559 }
560
561
562 size_t
563 term_vspan(const struct roffsu *su)
564 {
565 double r;
566
567 switch (su->unit) {
568 case (SCALE_CM):
569 r = su->scale * 2;
570 break;
571 case (SCALE_IN):
572 r = su->scale * 6;
573 break;
574 case (SCALE_PC):
575 r = su->scale;
576 break;
577 case (SCALE_PT):
578 r = su->scale / 8;
579 break;
580 case (SCALE_MM):
581 r = su->scale / 1000;
582 break;
583 case (SCALE_VS):
584 r = su->scale;
585 break;
586 default:
587 r = su->scale - 1;
588 break;
589 }
590
591 if (r < 0.0)
592 r = 0.0;
593 return(/* LINTED */(size_t)
594 r);
595 }
596
597
598 size_t
599 term_hspan(const struct roffsu *su)
600 {
601 double r;
602
603 /* XXX: CM, IN, and PT are approximations. */
604
605 switch (su->unit) {
606 case (SCALE_CM):
607 r = 4 * su->scale;
608 break;
609 case (SCALE_IN):
610 /* XXX: this is an approximation. */
611 r = 10 * su->scale;
612 break;
613 case (SCALE_PC):
614 r = (10 * su->scale) / 6;
615 break;
616 case (SCALE_PT):
617 r = (10 * su->scale) / 72;
618 break;
619 case (SCALE_MM):
620 r = su->scale / 1000; /* FIXME: double-check. */
621 break;
622 case (SCALE_VS):
623 r = su->scale * 2 - 1; /* FIXME: double-check. */
624 break;
625 default:
626 r = su->scale;
627 break;
628 }
629
630 if (r < 0.0)
631 r = 0.0;
632 return((size_t)/* LINTED */
633 r);
634 }
635
636