]> git.cameronkatri.com Git - mandoc.git/blob - term.c
Fix in `Mt' mailto from a patch by Tim van der Molen.
[mandoc.git] / term.c
1 /* $Id: term.c,v 1.130 2010/04/03 12:46:35 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 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20
21 #include <sys/types.h>
22
23 #include <assert.h>
24 #include <ctype.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <time.h>
29
30 #include "chars.h"
31 #include "out.h"
32 #include "term.h"
33 #include "man.h"
34 #include "mdoc.h"
35 #include "main.h"
36
37 static struct termp *term_alloc(enum termenc);
38 static void term_free(struct termp *);
39 static void spec(struct termp *, const char *, size_t);
40 static void res(struct termp *, const char *, size_t);
41 static void buffera(struct termp *, const char *, size_t);
42 static void bufferc(struct termp *, char);
43 static void adjbuf(struct termp *p, size_t);
44 static void encode(struct termp *, const char *, size_t);
45
46
47 void *
48 ascii_alloc(void)
49 {
50
51 return(term_alloc(TERMENC_ASCII));
52 }
53
54
55 void
56 terminal_free(void *arg)
57 {
58
59 term_free((struct termp *)arg);
60 }
61
62
63 static void
64 term_free(struct termp *p)
65 {
66
67 if (p->buf)
68 free(p->buf);
69 if (p->symtab)
70 chars_free(p->symtab);
71
72 free(p);
73 }
74
75
76 static struct termp *
77 term_alloc(enum termenc enc)
78 {
79 struct termp *p;
80
81 p = calloc(1, sizeof(struct termp));
82 if (NULL == p) {
83 perror(NULL);
84 exit(EXIT_FAILURE);
85 }
86 p->enc = enc;
87 return(p);
88 }
89
90
91 /*
92 * Flush a line of text. A "line" is loosely defined as being something
93 * that should be followed by a newline, regardless of whether it's
94 * broken apart by newlines getting there. A line can also be a
95 * fragment of a columnar list (`Bl -tag' or `Bl -column'), which does
96 * not have a trailing newline.
97 *
98 * The following flags may be specified:
99 *
100 * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
101 * offset value. This is useful when doing columnar lists where the
102 * prior column has right-padded.
103 *
104 * - TERMP_NOBREAK: this is the most important and is used when making
105 * columns. In short: don't print a newline and instead pad to the
106 * right margin. Used in conjunction with TERMP_NOLPAD.
107 *
108 * - TERMP_TWOSPACE: when padding, make sure there are at least two
109 * space characters of padding. Otherwise, rather break the line.
110 *
111 * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and
112 * the line is overrun, and don't pad-right if it's underrun.
113 *
114 * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when
115 * overruning, instead save the position and continue at that point
116 * when the next invocation.
117 *
118 * In-line line breaking:
119 *
120 * If TERMP_NOBREAK is specified and the line overruns the right
121 * margin, it will break and pad-right to the right margin after
122 * writing. If maxrmargin is violated, it will break and continue
123 * writing from the right-margin, which will lead to the above scenario
124 * upon exit. Otherwise, the line will break at the right margin.
125 */
126 void
127 term_flushln(struct termp *p)
128 {
129 int i; /* current input position in p->buf */
130 size_t vis; /* current visual position on output */
131 size_t vbl; /* number of blanks to prepend to output */
132 size_t vsz; /* visual characters to write to output */
133 size_t bp; /* visual right border position */
134 size_t hyph; /* visible position of hyphen */
135 int j; /* temporary loop index */
136 size_t maxvis, mmax;
137
138 /*
139 * First, establish the maximum columns of "visible" content.
140 * This is usually the difference between the right-margin and
141 * an indentation, but can be, for tagged lists or columns, a
142 * small set of values.
143 */
144
145 assert(p->offset < p->rmargin);
146
147 maxvis = (int)(p->rmargin - p->offset) - p->overstep < 0 ?
148 /* LINTED */
149 0 : p->rmargin - p->offset - p->overstep;
150 mmax = (int)(p->maxrmargin - p->offset) - p->overstep < 0 ?
151 /* LINTED */
152 0 : p->maxrmargin - p->offset - p->overstep;
153
154 bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
155
156 /*
157 * FIXME: if bp is zero, we still output the first word before
158 * breaking the line.
159 */
160
161 vis = 0;
162
163 /*
164 * If in the standard case (left-justified), then begin with our
165 * indentation, otherwise (columns, etc.) just start spitting
166 * out text.
167 */
168
169 if ( ! (p->flags & TERMP_NOLPAD))
170 /* LINTED */
171 for (j = 0; j < (int)p->offset; j++)
172 putchar(' ');
173
174 for (i = 0; i < (int)p->col; i++) {
175 /*
176 * Count up visible word characters. Control sequences
177 * (starting with the CSI) aren't counted. A space
178 * generates a non-printing word, which is valid (the
179 * space is printed according to regular spacing rules).
180 * Collect the number of printable characters until the
181 * first hyphen, if found. Hyphens aren't included if
182 * they're the first character (so `Fl' doesn't break)
183 * or second consecutive character (`Fl -').
184 */
185
186 /* LINTED */
187 for (j = i, vsz = 0, hyph = 0; j < (int)p->col; j++) {
188 if (j && ' ' == p->buf[j])
189 break;
190 if (8 == p->buf[j])
191 vsz--;
192 else
193 vsz++;
194 if (j > i && '-' == p->buf[j] && 0 == hyph)
195 if ('-' != p->buf[j - 1])
196 hyph = vsz;
197 }
198
199 /*
200 * Choose the number of blanks to prepend: no blank at the
201 * beginning of a line, one between words -- but do not
202 * actually write them yet.
203 */
204
205 vbl = (size_t)(0 == vis ? 0 : 1);
206
207 /*
208 * Find out whether we would exceed the right margin.
209 * If so, break to the next line, possibly after
210 * emittign character up to a hyphen. Otherwise, write
211 * the chosen number of blanks.
212 */
213
214 if (vis && vis + vbl + vsz > bp) {
215 /*
216 * Has a hyphen been found before the breakpoint
217 * that we can use?
218 */
219 if (hyph && vis + vbl + hyph <= bp) {
220 /* First prepend blanks. */
221 for (j = 0; j < (int)vbl; j++)
222 putchar(' ');
223
224 /* Emit up to the character. */
225 do {
226 if (31 == p->buf[i])
227 putchar(' ');
228 else
229 putchar(p->buf[i]);
230 if (8 != p->buf[i])
231 vsz--;
232 } while ('-' != p->buf[i++]);
233
234 /* Emit trailing decoration. */
235 if (8 == p->buf[i]) {
236 putchar(p->buf[i]);
237 putchar(p->buf[i + 1]);
238 }
239 }
240
241 putchar('\n');
242 if (TERMP_NOBREAK & p->flags) {
243 for (j = 0; j < (int)p->rmargin; j++)
244 putchar(' ');
245 vis = p->rmargin - p->offset;
246 } else {
247 for (j = 0; j < (int)p->offset; j++)
248 putchar(' ');
249 vis = 0;
250 }
251
252 /* Remove the p->overstep width. */
253
254 bp += (int)/* LINTED */
255 p->overstep;
256 p->overstep = 0;
257 } else {
258 for (j = 0; j < (int)vbl; j++)
259 putchar(' ');
260 vis += vbl;
261 }
262
263 /* Write out the [remaining] word. */
264 for ( ; i < (int)p->col; i++)
265 if (' ' == p->buf[i])
266 break;
267 else if (31 == p->buf[i])
268 putchar(' ');
269 else
270 putchar(p->buf[i]);
271
272 vis += vsz;
273 }
274
275 p->col = 0;
276 p->overstep = 0;
277
278 if ( ! (TERMP_NOBREAK & p->flags)) {
279 putchar('\n');
280 return;
281 }
282
283 if (TERMP_HANG & p->flags) {
284 /* We need one blank after the tag. */
285 p->overstep = /* LINTED */
286 vis - maxvis + 1;
287
288 /*
289 * Behave exactly the same way as groff:
290 * If we have overstepped the margin, temporarily move
291 * it to the right and flag the rest of the line to be
292 * shorter.
293 * If we landed right at the margin, be happy.
294 * If we are one step before the margin, temporarily
295 * move it one step LEFT and flag the rest of the line
296 * to be longer.
297 */
298 if (p->overstep >= -1) {
299 assert((int)maxvis + p->overstep >= 0);
300 /* LINTED */
301 maxvis += p->overstep;
302 } else
303 p->overstep = 0;
304
305 } else if (TERMP_DANGLE & p->flags)
306 return;
307
308 /* Right-pad. */
309 if (maxvis > vis + /* LINTED */
310 ((TERMP_TWOSPACE & p->flags) ? 1 : 0))
311 for ( ; vis < maxvis; vis++)
312 putchar(' ');
313 else { /* ...or newline break. */
314 putchar('\n');
315 for (i = 0; i < (int)p->rmargin; i++)
316 putchar(' ');
317 }
318 }
319
320
321 /*
322 * A newline only breaks an existing line; it won't assert vertical
323 * space. All data in the output buffer is flushed prior to the newline
324 * assertion.
325 */
326 void
327 term_newln(struct termp *p)
328 {
329
330 p->flags |= TERMP_NOSPACE;
331 if (0 == p->col) {
332 p->flags &= ~TERMP_NOLPAD;
333 return;
334 }
335 term_flushln(p);
336 p->flags &= ~TERMP_NOLPAD;
337 }
338
339
340 /*
341 * Asserts a vertical space (a full, empty line-break between lines).
342 * Note that if used twice, this will cause two blank spaces and so on.
343 * All data in the output buffer is flushed prior to the newline
344 * assertion.
345 */
346 void
347 term_vspace(struct termp *p)
348 {
349
350 term_newln(p);
351 putchar('\n');
352 }
353
354
355 static void
356 spec(struct termp *p, const char *word, size_t len)
357 {
358 const char *rhs;
359 size_t sz;
360
361 rhs = chars_a2ascii(p->symtab, word, len, &sz);
362 if (rhs)
363 encode(p, rhs, sz);
364 }
365
366
367 static void
368 res(struct termp *p, const char *word, size_t len)
369 {
370 const char *rhs;
371 size_t sz;
372
373 rhs = chars_a2res(p->symtab, word, len, &sz);
374 if (rhs)
375 encode(p, rhs, sz);
376 }
377
378
379 void
380 term_fontlast(struct termp *p)
381 {
382 enum termfont f;
383
384 f = p->fontl;
385 p->fontl = p->fontq[p->fonti];
386 p->fontq[p->fonti] = f;
387 }
388
389
390 void
391 term_fontrepl(struct termp *p, enum termfont f)
392 {
393
394 p->fontl = p->fontq[p->fonti];
395 p->fontq[p->fonti] = f;
396 }
397
398
399 void
400 term_fontpush(struct termp *p, enum termfont f)
401 {
402
403 assert(p->fonti + 1 < 10);
404 p->fontl = p->fontq[p->fonti];
405 p->fontq[++p->fonti] = f;
406 }
407
408
409 const void *
410 term_fontq(struct termp *p)
411 {
412
413 return(&p->fontq[p->fonti]);
414 }
415
416
417 enum termfont
418 term_fonttop(struct termp *p)
419 {
420
421 return(p->fontq[p->fonti]);
422 }
423
424
425 void
426 term_fontpopq(struct termp *p, const void *key)
427 {
428
429 while (p->fonti >= 0 && key != &p->fontq[p->fonti])
430 p->fonti--;
431 assert(p->fonti >= 0);
432 }
433
434
435 void
436 term_fontpop(struct termp *p)
437 {
438
439 assert(p->fonti);
440 p->fonti--;
441 }
442
443
444 /*
445 * Handle pwords, partial words, which may be either a single word or a
446 * phrase that cannot be broken down (such as a literal string). This
447 * handles word styling.
448 */
449 void
450 term_word(struct termp *p, const char *word)
451 {
452 const char *sv, *seq;
453 int sz;
454 size_t ssz;
455 enum roffdeco deco;
456
457 sv = word;
458
459 if (word[0] && '\0' == word[1])
460 switch (word[0]) {
461 case('.'):
462 /* FALLTHROUGH */
463 case(','):
464 /* FALLTHROUGH */
465 case(';'):
466 /* FALLTHROUGH */
467 case(':'):
468 /* FALLTHROUGH */
469 case('?'):
470 /* FALLTHROUGH */
471 case('!'):
472 /* FALLTHROUGH */
473 case(')'):
474 /* FALLTHROUGH */
475 case(']'):
476 if ( ! (TERMP_IGNDELIM & p->flags))
477 p->flags |= TERMP_NOSPACE;
478 break;
479 default:
480 break;
481 }
482
483 if ( ! (TERMP_NOSPACE & p->flags))
484 bufferc(p, ' ');
485
486 if ( ! (p->flags & TERMP_NONOSPACE))
487 p->flags &= ~TERMP_NOSPACE;
488
489 /* FIXME: use strcspn. */
490
491 while (*word) {
492 if ('\\' != *word) {
493 encode(p, word, 1);
494 word++;
495 continue;
496 }
497
498 seq = ++word;
499 sz = a2roffdeco(&deco, &seq, &ssz);
500
501 switch (deco) {
502 case (DECO_RESERVED):
503 res(p, seq, ssz);
504 break;
505 case (DECO_SPECIAL):
506 spec(p, seq, ssz);
507 break;
508 case (DECO_BOLD):
509 term_fontrepl(p, TERMFONT_BOLD);
510 break;
511 case (DECO_ITALIC):
512 term_fontrepl(p, TERMFONT_UNDER);
513 break;
514 case (DECO_ROMAN):
515 term_fontrepl(p, TERMFONT_NONE);
516 break;
517 case (DECO_PREVIOUS):
518 term_fontlast(p);
519 break;
520 default:
521 break;
522 }
523
524 word += sz;
525 if (DECO_NOSPACE == deco && '\0' == *word)
526 p->flags |= TERMP_NOSPACE;
527 }
528
529 if (sv[0] && 0 == sv[1])
530 switch (sv[0]) {
531 case('|'):
532 /* FALLTHROUGH */
533 case('('):
534 /* FALLTHROUGH */
535 case('['):
536 p->flags |= TERMP_NOSPACE;
537 break;
538 default:
539 break;
540 }
541 }
542
543
544 static void
545 adjbuf(struct termp *p, size_t sz)
546 {
547
548 if (0 == p->maxcols)
549 p->maxcols = 1024;
550 while (sz >= p->maxcols)
551 p->maxcols <<= 2;
552
553 p->buf = realloc(p->buf, p->maxcols);
554 if (NULL == p->buf) {
555 perror(NULL);
556 exit(EXIT_FAILURE);
557 }
558 }
559
560
561 static void
562 buffera(struct termp *p, const char *word, size_t sz)
563 {
564
565 if (p->col + sz >= p->maxcols)
566 adjbuf(p, p->col + sz);
567
568 memcpy(&p->buf[(int)p->col], word, sz);
569 p->col += sz;
570 }
571
572
573 static void
574 bufferc(struct termp *p, char c)
575 {
576
577 if (p->col + 1 >= p->maxcols)
578 adjbuf(p, p->col + 1);
579
580 p->buf[(int)p->col++] = c;
581 }
582
583
584 static void
585 encode(struct termp *p, const char *word, size_t sz)
586 {
587 enum termfont f;
588 int i;
589
590 /*
591 * Encode and buffer a string of characters. If the current
592 * font mode is unset, buffer directly, else encode then buffer
593 * character by character.
594 */
595
596 if (TERMFONT_NONE == (f = term_fonttop(p))) {
597 buffera(p, word, sz);
598 return;
599 }
600
601 for (i = 0; i < (int)sz; i++) {
602 if ( ! isgraph((u_char)word[i])) {
603 bufferc(p, word[i]);
604 continue;
605 }
606
607 if (TERMFONT_UNDER == f)
608 bufferc(p, '_');
609 else
610 bufferc(p, word[i]);
611
612 bufferc(p, 8);
613 bufferc(p, word[i]);
614 }
615 }
616
617
618 size_t
619 term_vspan(const struct roffsu *su)
620 {
621 double r;
622
623 switch (su->unit) {
624 case (SCALE_CM):
625 r = su->scale * 2;
626 break;
627 case (SCALE_IN):
628 r = su->scale * 6;
629 break;
630 case (SCALE_PC):
631 r = su->scale;
632 break;
633 case (SCALE_PT):
634 r = su->scale / 8;
635 break;
636 case (SCALE_MM):
637 r = su->scale / 1000;
638 break;
639 case (SCALE_VS):
640 r = su->scale;
641 break;
642 default:
643 r = su->scale - 1;
644 break;
645 }
646
647 if (r < 0.0)
648 r = 0.0;
649 return(/* LINTED */(size_t)
650 r);
651 }
652
653
654 size_t
655 term_hspan(const struct roffsu *su)
656 {
657 double r;
658
659 /* XXX: CM, IN, and PT are approximations. */
660
661 switch (su->unit) {
662 case (SCALE_CM):
663 r = 4 * su->scale;
664 break;
665 case (SCALE_IN):
666 /* XXX: this is an approximation. */
667 r = 10 * su->scale;
668 break;
669 case (SCALE_PC):
670 r = (10 * su->scale) / 6;
671 break;
672 case (SCALE_PT):
673 r = (10 * su->scale) / 72;
674 break;
675 case (SCALE_MM):
676 r = su->scale / 1000; /* FIXME: double-check. */
677 break;
678 case (SCALE_VS):
679 r = su->scale * 2 - 1; /* FIXME: double-check. */
680 break;
681 default:
682 r = su->scale;
683 break;
684 }
685
686 if (r < 0.0)
687 r = 0.0;
688 return((size_t)/* LINTED */
689 r);
690 }
691
692