mandoc(1): Update to 1.9.13.
[dragonfly.git] / usr.bin / mandoc / term.c
1 /*      $Id: term.c,v 1.120 2009/10/31 06:10:58 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 <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <time.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         p = calloc(1, sizeof(struct termp));
80         if (NULL == p) {
81                 perror(NULL);
82                 exit(EXIT_FAILURE);
83         }
84         p->maxrmargin = 78;
85         p->enc = enc;
86         return(p);
87 }
88
89
90 /*
91  * Flush a line of text.  A "line" is loosely defined as being something
92  * that should be followed by a newline, regardless of whether it's
93  * broken apart by newlines getting there.  A line can also be a
94  * fragment of a columnar list.
95  *
96  * Specifically, a line is whatever's in p->buf of length p->col, which
97  * is zeroed after this function returns.
98  *
99  * The usage of termp:flags is as follows:
100  *
101  *  - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
102  *    offset value.  This is useful when doing columnar lists where the
103  *    prior column has right-padded.
104  *
105  *  - TERMP_NOBREAK: this is the most important and is used when making
106  *    columns.  In short: don't print a newline and instead pad to the
107  *    right margin.  Used in conjunction with TERMP_NOLPAD.
108  *
109  *  - TERMP_TWOSPACE: when padding, make sure there are at least two
110  *    space characters of padding.  Otherwise, rather break the line.
111  *
112  *  - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and
113  *    the line is overrun, and don't pad-right if it's underrun.
114  *
115  *  - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when
116  *    overruning, instead save the position and continue at that point
117  *    when the next invocation.
118  *
119  *  In-line line breaking:
120  *
121  *  If TERMP_NOBREAK is specified and the line overruns the right
122  *  margin, it will break and pad-right to the right margin after
123  *  writing.  If maxrmargin is violated, it will break and continue
124  *  writing from the right-margin, which will lead to the above scenario
125  *  upon exit.  Otherwise, the line will break at the right margin.
126  */
127 void
128 term_flushln(struct termp *p)
129 {
130         int              i;     /* current input position in p->buf */
131         size_t           vis;   /* current visual position on output */
132         size_t           vbl;   /* number of blanks to prepend to output */
133         size_t           vsz;   /* visual characters to write to output */
134         size_t           bp;    /* visual right border position */
135         int              j;     /* temporary loop index */
136         size_t           maxvis, mmax;
137         static int       overstep = 0;
138
139         /*
140          * First, establish the maximum columns of "visible" content.
141          * This is usually the difference between the right-margin and
142          * an indentation, but can be, for tagged lists or columns, a
143          * small set of values.
144          */
145
146         assert(p->offset < p->rmargin);
147
148         maxvis = (int)(p->rmargin - p->offset) - overstep < 0 ?
149                 /* LINTED */
150                 0 : p->rmargin - p->offset - overstep;
151         mmax = (int)(p->maxrmargin - p->offset) - overstep < 0 ?
152                 /* LINTED */
153                 0 : p->maxrmargin - p->offset - overstep;
154
155         bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
156
157         /*
158          * FIXME: if bp is zero, we still output the first word before
159          * breaking the line.
160          */
161
162         vis = 0;
163
164         /*
165          * If in the standard case (left-justified), then begin with our
166          * indentation, otherwise (columns, etc.) just start spitting
167          * out text.
168          */
169
170         if ( ! (p->flags & TERMP_NOLPAD))
171                 /* LINTED */
172                 for (j = 0; j < (int)p->offset; j++)
173                         putchar(' ');
174
175         for (i = 0; i < (int)p->col; i++) {
176                 /*
177                  * Count up visible word characters.  Control sequences
178                  * (starting with the CSI) aren't counted.  A space
179                  * generates a non-printing word, which is valid (the
180                  * space is printed according to regular spacing rules).
181                  */
182
183                 /* LINTED */
184                 for (j = i, vsz = 0; j < (int)p->col; j++) {
185                         if (j && ' ' == p->buf[j])
186                                 break;
187                         else if (8 == p->buf[j])
188                                 vsz--;
189                         else
190                                 vsz++;
191                 }
192
193                 /*
194                  * Choose the number of blanks to prepend: no blank at the
195                  * beginning of a line, one between words -- but do not
196                  * actually write them yet.
197                  */
198                 vbl = (size_t)(0 == vis ? 0 : 1);
199
200                 /*
201                  * Find out whether we would exceed the right margin.
202                  * If so, break to the next line.  (TODO: hyphenate)
203                  * Otherwise, write the chosen number of blanks now.
204                  */
205                 if (vis && vis + vbl + vsz > bp) {
206                         putchar('\n');
207                         if (TERMP_NOBREAK & p->flags) {
208                                 for (j = 0; j < (int)p->rmargin; j++)
209                                         putchar(' ');
210                                 vis = p->rmargin - p->offset;
211                         } else {
212                                 for (j = 0; j < (int)p->offset; j++)
213                                         putchar(' ');
214                                 vis = 0;
215                         }
216                         /* Remove the overstep width. */
217                         bp += (int)/* LINTED */
218                                 overstep;
219                         overstep = 0;
220                 } else {
221                         for (j = 0; j < (int)vbl; j++)
222                                 putchar(' ');
223                         vis += vbl;
224                 }
225
226                 /*
227                  * Finally, write out the word.
228                  */
229                 for ( ; i < (int)p->col; i++) {
230                         if (' ' == p->buf[i])
231                                 break;
232                         putchar(p->buf[i]);
233                 }
234                 vis += vsz;
235         }
236
237         p->col = 0;
238         overstep = 0;
239
240         if ( ! (TERMP_NOBREAK & p->flags)) {
241                 putchar('\n');
242                 return;
243         }
244
245         if (TERMP_HANG & p->flags) {
246                 /* We need one blank after the tag. */
247                 overstep = /* LINTED */
248                         vis - maxvis + 1;
249
250                 /*
251                  * Behave exactly the same way as groff:
252                  * If we have overstepped the margin, temporarily move
253                  * it to the right and flag the rest of the line to be
254                  * shorter.
255                  * If we landed right at the margin, be happy.
256                  * If we are one step before the margin, temporarily
257                  * move it one step LEFT and flag the rest of the line
258                  * to be longer.
259                  */
260                 if (overstep >= -1) {
261                         assert((int)maxvis + overstep >= 0);
262                         /* LINTED */
263                         maxvis += overstep;
264                 } else
265                         overstep = 0;
266
267         } else if (TERMP_DANGLE & p->flags)
268                 return;
269
270         /* Right-pad. */
271         if (maxvis > vis + /* LINTED */
272                         ((TERMP_TWOSPACE & p->flags) ? 1 : 0))
273                 for ( ; vis < maxvis; vis++)
274                         putchar(' ');
275         else {  /* ...or newline break. */
276                 putchar('\n');
277                 for (i = 0; i < (int)p->rmargin; i++)
278                         putchar(' ');
279         }
280 }
281
282
283 /*
284  * A newline only breaks an existing line; it won't assert vertical
285  * space.  All data in the output buffer is flushed prior to the newline
286  * assertion.
287  */
288 void
289 term_newln(struct termp *p)
290 {
291
292         p->flags |= TERMP_NOSPACE;
293         if (0 == p->col) {
294                 p->flags &= ~TERMP_NOLPAD;
295                 return;
296         }
297         term_flushln(p);
298         p->flags &= ~TERMP_NOLPAD;
299 }
300
301
302 /*
303  * Asserts a vertical space (a full, empty line-break between lines).
304  * Note that if used twice, this will cause two blank spaces and so on.
305  * All data in the output buffer is flushed prior to the newline
306  * assertion.
307  */
308 void
309 term_vspace(struct termp *p)
310 {
311
312         term_newln(p);
313         putchar('\n');
314 }
315
316
317 static void
318 do_special(struct termp *p, const char *word, size_t len)
319 {
320         const char      *rhs;
321         size_t           sz;
322         int              i;
323
324         rhs = chars_a2ascii(p->symtab, word, len, &sz);
325
326         if (NULL == rhs) {
327 #if 0
328                 fputs("Unknown special character: ", stderr);
329                 for (i = 0; i < (int)len; i++)
330                         fputc(word[i], stderr);
331                 fputc('\n', stderr);
332 #endif
333                 return;
334         }
335         for (i = 0; i < (int)sz; i++)
336                 encode(p, rhs[i]);
337 }
338
339
340 static void
341 do_reserved(struct termp *p, const char *word, size_t len)
342 {
343         const char      *rhs;
344         size_t           sz;
345         int              i;
346
347         rhs = chars_a2res(p->symtab, word, len, &sz);
348
349         if (NULL == rhs) {
350 #if 0
351                 fputs("Unknown reserved word: ", stderr);
352                 for (i = 0; i < (int)len; i++)
353                         fputc(word[i], stderr);
354                 fputc('\n', stderr);
355 #endif
356                 return;
357         }
358         for (i = 0; i < (int)sz; i++)
359                 encode(p, rhs[i]);
360 }
361
362
363 /*
364  * Handle an escape sequence: determine its length and pass it to the
365  * escape-symbol look table.  Note that we assume mdoc(3) has validated
366  * the escape sequence (we assert upon badly-formed escape sequences).
367  */
368 static void
369 do_escaped(struct termp *p, const char **word)
370 {
371         int              j, type;
372         const char      *wp;
373
374         wp = *word;
375         type = 1;
376
377         if (0 == *(++wp)) {
378                 *word = wp;
379                 return;
380         }
381
382         if ('(' == *wp) {
383                 wp++;
384                 if (0 == *wp || 0 == *(wp + 1)) {
385                         *word = 0 == *wp ? wp : wp + 1;
386                         return;
387                 }
388
389                 do_special(p, wp, 2);
390                 *word = ++wp;
391                 return;
392
393         } else if ('*' == *wp) {
394                 if (0 == *(++wp)) {
395                         *word = wp;
396                         return;
397                 }
398
399                 switch (*wp) {
400                 case ('('):
401                         wp++;
402                         if (0 == *wp || 0 == *(wp + 1)) {
403                                 *word = 0 == *wp ? wp : wp + 1;
404                                 return;
405                         }
406
407                         do_reserved(p, wp, 2);
408                         *word = ++wp;
409                         return;
410                 case ('['):
411                         type = 0;
412                         break;
413                 default:
414                         do_reserved(p, wp, 1);
415                         *word = wp;
416                         return;
417                 }
418
419         } else if ('f' == *wp) {
420                 if (0 == *(++wp)) {
421                         *word = wp;
422                         return;
423                 }
424
425                 switch (*wp) {
426                 case ('B'):
427                         p->bold++;
428                         break;
429                 case ('I'):
430                         p->under++;
431                         break;
432                 case ('P'):
433                         /* FALLTHROUGH */
434                 case ('R'):
435                         p->bold = p->under = 0;
436                         break;
437                 default:
438                         break;
439                 }
440
441                 *word = wp;
442                 return;
443
444         } else if ('[' != *wp) {
445                 do_special(p, wp, 1);
446                 *word = wp;
447                 return;
448         }
449
450         wp++;
451         for (j = 0; *wp && ']' != *wp; wp++, j++)
452                 /* Loop... */ ;
453
454         if (0 == *wp) {
455                 *word = wp;
456                 return;
457         }
458
459         if (type)
460                 do_special(p, wp - j, (size_t)j);
461         else
462                 do_reserved(p, wp - j, (size_t)j);
463         *word = wp;
464 }
465
466
467 /*
468  * Handle pwords, partial words, which may be either a single word or a
469  * phrase that cannot be broken down (such as a literal string).  This
470  * handles word styling.
471  */
472 void
473 term_word(struct termp *p, const char *word)
474 {
475         const char       *sv;
476
477         sv = word;
478
479         if (word[0] && 0 == word[1])
480                 switch (word[0]) {
481                 case('.'):
482                         /* FALLTHROUGH */
483                 case(','):
484                         /* FALLTHROUGH */
485                 case(';'):
486                         /* FALLTHROUGH */
487                 case(':'):
488                         /* FALLTHROUGH */
489                 case('?'):
490                         /* FALLTHROUGH */
491                 case('!'):
492                         /* FALLTHROUGH */
493                 case(')'):
494                         /* FALLTHROUGH */
495                 case(']'):
496                         /* FALLTHROUGH */
497                 case('}'):
498                         if ( ! (TERMP_IGNDELIM & p->flags))
499                                 p->flags |= TERMP_NOSPACE;
500                         break;
501                 default:
502                         break;
503                 }
504
505         if ( ! (TERMP_NOSPACE & p->flags))
506                 buffer(p, ' ');
507
508         if ( ! (p->flags & TERMP_NONOSPACE))
509                 p->flags &= ~TERMP_NOSPACE;
510
511         for ( ; *word; word++)
512                 if ('\\' != *word)
513                         encode(p, *word);
514                 else
515                         do_escaped(p, &word);
516
517         if (sv[0] && 0 == sv[1])
518                 switch (sv[0]) {
519                 case('('):
520                         /* FALLTHROUGH */
521                 case('['):
522                         /* FALLTHROUGH */
523                 case('{'):
524                         p->flags |= TERMP_NOSPACE;
525                         break;
526                 default:
527                         break;
528                 }
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 buffer(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                         perror(NULL);
549                         exit(EXIT_FAILURE);
550                 }
551                 p->maxcols = s;
552         }
553         p->buf[(int)(p->col)++] = c;
554 }
555
556
557 static void
558 encode(struct termp *p, char c)
559 {
560
561         if (' ' != c) {
562                 if (p->under) {
563                         buffer(p, '_');
564                         buffer(p, 8);
565                 }
566                 if (p->bold) {
567                         buffer(p, c);
568                         buffer(p, 8);
569                 }
570         }
571         buffer(p, c);
572 }
573
574
575 size_t
576 term_vspan(const struct roffsu *su)
577 {
578         double           r;
579
580         switch (su->unit) {
581         case (SCALE_CM):
582                 r = su->scale * 2;
583                 break;
584         case (SCALE_IN):
585                 r = su->scale * 6;
586                 break;
587         case (SCALE_PC):
588                 r = su->scale;
589                 break;
590         case (SCALE_PT):
591                 r = su->scale / 8;
592                 break;
593         case (SCALE_MM):
594                 r = su->scale / 1000;
595                 break;
596         case (SCALE_VS):
597                 r = su->scale;
598                 break;
599         default:
600                 r = su->scale - 1;
601                 break;
602         }
603
604         if (r < 0.0)
605                 r = 0.0;
606         return(/* LINTED */(size_t)
607                         r);
608 }
609
610
611 size_t
612 term_hspan(const struct roffsu *su)
613 {
614         double           r;
615
616         /* XXX: CM, IN, and PT are approximations. */
617
618         switch (su->unit) {
619         case (SCALE_CM):
620                 r = 4 * su->scale;
621                 break;
622         case (SCALE_IN):
623                 /* XXX: this is an approximation. */
624                 r = 10 * su->scale;
625                 break;
626         case (SCALE_PC):
627                 r = (10 * su->scale) / 6;
628                 break;
629         case (SCALE_PT):
630                 r = (10 * su->scale) / 72;
631                 break;
632         case (SCALE_MM):
633                 r = su->scale / 1000; /* FIXME: double-check. */
634                 break;
635         case (SCALE_VS):
636                 r = su->scale * 2 - 1; /* FIXME: double-check. */
637                 break;
638         default:
639                 r = su->scale;
640                 break;
641         }
642
643         if (r < 0.0)
644                 r = 0.0;
645         return((size_t)/* LINTED */
646                         r);
647 }