Merge branch 'vendor/TEXINFO'
[dragonfly.git] / contrib / mdocml / out.c
1 /*      $Id: out.c,v 1.39 2011/03/17 08:49:34 kristaps Exp $ */
2 /*
3  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21
22 #include <sys/types.h>
23
24 #include <assert.h>
25 #include <ctype.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30
31 #include "mandoc.h"
32 #include "out.h"
33
34 static  void    tblcalc_data(struct rofftbl *, struct roffcol *,
35                         const struct tbl *, const struct tbl_dat *);
36 static  void    tblcalc_literal(struct rofftbl *, struct roffcol *,
37                         const struct tbl_dat *);
38 static  void    tblcalc_number(struct rofftbl *, struct roffcol *,
39                         const struct tbl *, const struct tbl_dat *);
40
41 /* 
42  * Convert a `scaling unit' to a consistent form, or fail.  Scaling
43  * units are documented in groff.7, mdoc.7, man.7.
44  */
45 int
46 a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
47 {
48         char             buf[BUFSIZ], hasd;
49         int              i;
50         enum roffscale   unit;
51
52         if ('\0' == *src)
53                 return(0);
54
55         i = hasd = 0;
56
57         switch (*src) {
58         case ('+'):
59                 src++;
60                 break;
61         case ('-'):
62                 buf[i++] = *src++;
63                 break;
64         default:
65                 break;
66         }
67
68         if ('\0' == *src)
69                 return(0);
70
71         while (i < BUFSIZ) {
72                 if ( ! isdigit((u_char)*src)) {
73                         if ('.' != *src)
74                                 break;
75                         else if (hasd)
76                                 break;
77                         else
78                                 hasd = 1;
79                 }
80                 buf[i++] = *src++;
81         }
82
83         if (BUFSIZ == i || (*src && *(src + 1)))
84                 return(0);
85
86         buf[i] = '\0';
87
88         switch (*src) {
89         case ('c'):
90                 unit = SCALE_CM;
91                 break;
92         case ('i'):
93                 unit = SCALE_IN;
94                 break;
95         case ('P'):
96                 unit = SCALE_PC;
97                 break;
98         case ('p'):
99                 unit = SCALE_PT;
100                 break;
101         case ('f'):
102                 unit = SCALE_FS;
103                 break;
104         case ('v'):
105                 unit = SCALE_VS;
106                 break;
107         case ('m'):
108                 unit = SCALE_EM;
109                 break;
110         case ('\0'):
111                 if (SCALE_MAX == def)
112                         return(0);
113                 unit = SCALE_BU;
114                 break;
115         case ('u'):
116                 unit = SCALE_BU;
117                 break;
118         case ('M'):
119                 unit = SCALE_MM;
120                 break;
121         case ('n'):
122                 unit = SCALE_EN;
123                 break;
124         default:
125                 return(0);
126         }
127
128         /* FIXME: do this in the caller. */
129         if ((dst->scale = atof(buf)) < 0)
130                 dst->scale = 0;
131         dst->unit = unit;
132         return(1);
133 }
134
135
136 /*
137  * Correctly writes the time in nroff form, which differs from standard
138  * form in that a space isn't printed in lieu of the extra %e field for
139  * single-digit dates.
140  */
141 void
142 time2a(time_t t, char *dst, size_t sz)
143 {
144         struct tm        tm;
145         char             buf[5];
146         char            *p;
147         size_t           nsz;
148
149         assert(sz > 1);
150         localtime_r(&t, &tm);
151
152         p = dst;
153         nsz = 0;
154
155         dst[0] = '\0';
156
157         if (0 == (nsz = strftime(p, sz, "%B ", &tm)))
158                 return;
159
160         p += (int)nsz;
161         sz -= nsz;
162
163         if (0 == strftime(buf, sizeof(buf), "%e, ", &tm))
164                 return;
165
166         nsz = strlcat(p, buf + (' ' == buf[0] ? 1 : 0), sz);
167
168         if (nsz >= sz)
169                 return;
170
171         p += (int)nsz;
172         sz -= nsz;
173
174         (void)strftime(p, sz, "%Y", &tm);
175 }
176
177
178 int
179 a2roffdeco(enum roffdeco *d, const char **word, size_t *sz)
180 {
181         int              i, j, lim;
182         char             term, c;
183         const char      *wp;
184         enum roffdeco    dd;
185
186         *d = DECO_NONE;
187         lim = i = 0;
188         term = '\0';
189         wp = *word;
190
191         switch ((c = wp[i++])) {
192         case ('('):
193                 *d = DECO_SPECIAL;
194                 lim = 2;
195                 break;
196         case ('F'):
197                 /* FALLTHROUGH */
198         case ('f'):
199                 *d = 'F' == c ? DECO_FFONT : DECO_FONT;
200
201                 switch (wp[i++]) {
202                 case ('('):
203                         lim = 2;
204                         break;
205                 case ('['):
206                         term = ']';
207                         break;
208                 case ('3'):
209                         /* FALLTHROUGH */
210                 case ('B'):
211                         *d = DECO_BOLD;
212                         return(i);
213                 case ('2'):
214                         /* FALLTHROUGH */
215                 case ('I'):
216                         *d = DECO_ITALIC;
217                         return(i);
218                 case ('P'):
219                         *d = DECO_PREVIOUS;
220                         return(i);
221                 case ('1'):
222                         /* FALLTHROUGH */
223                 case ('R'):
224                         *d = DECO_ROMAN;
225                         return(i);
226                 default:
227                         i--;
228                         lim = 1;
229                         break;
230                 }
231                 break;
232         case ('k'):
233                 /* FALLTHROUGH */
234         case ('M'):
235                 /* FALLTHROUGH */
236         case ('m'):
237                 /* FALLTHROUGH */
238         case ('*'):
239                 if ('*' == c)
240                         *d = DECO_RESERVED;
241
242                 switch (wp[i++]) {
243                 case ('('):
244                         lim = 2;
245                         break;
246                 case ('['):
247                         term = ']';
248                         break;
249                 default:
250                         i--;
251                         lim = 1;
252                         break;
253                 }
254                 break;
255
256         case ('N'):
257
258                 /*
259                  * Sequence of characters:  backslash,  'N' (i = 0),
260                  * starting delimiter (i = 1), character number (i = 2).
261                  */
262
263                 *word = wp + 2;
264                 *sz = 0;
265
266                 /*
267                  * Cannot use a digit as a starting delimiter;
268                  * but skip the digit anyway.
269                  */
270
271                 if (isdigit((int)wp[1]))
272                         return(2);
273
274                 /*
275                  * Any non-digit terminates the character number.
276                  * That is, the terminating delimiter need not
277                  * match the starting delimiter.
278                  */
279
280                 for (i = 2; isdigit((int)wp[i]); i++)
281                         (*sz)++;
282
283                 /*
284                  * This is only a numbered character
285                  * if the character number has at least one digit.
286                  */
287
288                 if (*sz)
289                         *d = DECO_NUMBERED;
290
291                 /*
292                  * Skip the terminating delimiter, even if it does not
293                  * match, and even if there is no character number.
294                  */
295
296                 return(++i);
297
298         case ('h'):
299                 /* FALLTHROUGH */
300         case ('v'):
301                 /* FALLTHROUGH */
302         case ('s'):
303                 j = 0;
304                 if ('+' == wp[i] || '-' == wp[i]) {
305                         i++;
306                         j = 1;
307                 }
308
309                 switch (wp[i++]) {
310                 case ('('):
311                         lim = 2;
312                         break;
313                 case ('['):
314                         term = ']';
315                         break;
316                 case ('\''):
317                         term = '\'';
318                         break;
319                 case ('0'):
320                         j = 1;
321                         /* FALLTHROUGH */
322                 default:
323                         i--;
324                         lim = 1;
325                         break;
326                 }
327
328                 if ('+' == wp[i] || '-' == wp[i]) {
329                         if (j)
330                                 return(i);
331                         i++;
332                 } 
333
334                 /* Handle embedded numerical subexp or escape. */
335
336                 if ('(' == wp[i]) {
337                         while (wp[i] && ')' != wp[i])
338                                 if ('\\' == wp[i++]) {
339                                         /* Handle embedded escape. */
340                                         *word = &wp[i];
341                                         i += a2roffdeco(&dd, word, sz);
342                                 }
343
344                         if (')' == wp[i++])
345                                 break;
346
347                         *d = DECO_NONE;
348                         return(i - 1);
349                 } else if ('\\' == wp[i]) {
350                         *word = &wp[++i];
351                         i += a2roffdeco(&dd, word, sz);
352                 }
353
354                 break;
355         case ('['):
356                 *d = DECO_SPECIAL;
357                 term = ']';
358                 break;
359         case ('c'):
360                 *d = DECO_NOSPACE;
361                 return(i);
362         case ('z'):
363                 *d = DECO_NONE;
364                 if ('\\' == wp[i]) {
365                         *word = &wp[++i];
366                         return(i + a2roffdeco(&dd, word, sz));
367                 } else
368                         lim = 1;
369                 break;
370         case ('o'):
371                 /* FALLTHROUGH */
372         case ('w'):
373                 if ('\'' == wp[i++]) {
374                         term = '\'';
375                         break;
376                 } 
377                 /* FALLTHROUGH */
378         default:
379                 *d = DECO_SSPECIAL;
380                 i--;
381                 lim = 1;
382                 break;
383         }
384
385         assert(term || lim);
386         *word = &wp[i];
387
388         if (term) {
389                 j = i;
390                 while (wp[i] && wp[i] != term)
391                         i++;
392                 if ('\0' == wp[i]) {
393                         *d = DECO_NONE;
394                         return(i);
395                 }
396
397                 assert(i >= j);
398                 *sz = (size_t)(i - j);
399
400                 return(i + 1);
401         }
402
403         assert(lim > 0);
404         *sz = (size_t)lim;
405
406         for (j = 0; wp[i] && j < lim; j++)
407                 i++;
408         if (j < lim)
409                 *d = DECO_NONE;
410
411         return(i);
412 }
413
414 /*
415  * Calculate the abstract widths and decimal positions of columns in a
416  * table.  This routine allocates the columns structures then runs over
417  * all rows and cells in the table.  The function pointers in "tbl" are
418  * used for the actual width calculations.
419  */
420 void
421 tblcalc(struct rofftbl *tbl, const struct tbl_span *sp)
422 {
423         const struct tbl_dat    *dp;
424         const struct tbl_head   *hp;
425         struct roffcol          *col;
426
427         /*
428          * Allocate the master column specifiers.  These will hold the
429          * widths and decimal positions for all cells in the column.  It
430          * must be freed and nullified by the caller.
431          */
432
433         assert(NULL == tbl->cols);
434         tbl->cols = mandoc_calloc
435                 ((size_t)sp->tbl->cols, sizeof(struct roffcol));
436
437         hp = sp->head;
438
439         for ( ; sp; sp = sp->next) {
440                 if (TBL_SPAN_DATA != sp->pos)
441                         continue;
442                 /*
443                  * Account for the data cells in the layout, matching it
444                  * to data cells in the data section.
445                  */
446                 for (dp = sp->first; dp; dp = dp->next) {
447                         assert(dp->layout);
448                         col = &tbl->cols[dp->layout->head->ident];
449                         tblcalc_data(tbl, col, sp->tbl, dp);
450                 }
451         }
452
453         /* 
454          * Calculate width of the spanners.  These get one space for a
455          * vertical line, two for a double-vertical line. 
456          */
457
458         for ( ; hp; hp = hp->next) {
459                 col = &tbl->cols[hp->ident];
460                 switch (hp->pos) {
461                 case (TBL_HEAD_VERT):
462                         col->width = (*tbl->len)(1, tbl->arg);
463                         break;
464                 case (TBL_HEAD_DVERT):
465                         col->width = (*tbl->len)(2, tbl->arg);
466                         break;
467                 default:
468                         break;
469                 }
470         }
471 }
472
473 static void
474 tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
475                 const struct tbl *tp, const struct tbl_dat *dp)
476 {
477         size_t           sz;
478
479         /* Branch down into data sub-types. */
480
481         switch (dp->layout->pos) {
482         case (TBL_CELL_HORIZ):
483                 /* FALLTHROUGH */
484         case (TBL_CELL_DHORIZ):
485                 sz = (*tbl->len)(1, tbl->arg);
486                 if (col->width < sz)
487                         col->width = sz;
488                 break;
489         case (TBL_CELL_LONG):
490                 /* FALLTHROUGH */
491         case (TBL_CELL_CENTRE):
492                 /* FALLTHROUGH */
493         case (TBL_CELL_LEFT):
494                 /* FALLTHROUGH */
495         case (TBL_CELL_RIGHT):
496                 tblcalc_literal(tbl, col, dp);
497                 break;
498         case (TBL_CELL_NUMBER):
499                 tblcalc_number(tbl, col, tp, dp);
500                 break;
501         case (TBL_CELL_DOWN):
502                 break;
503         default:
504                 abort();
505                 /* NOTREACHED */
506         }
507 }
508
509 static void
510 tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
511                 const struct tbl_dat *dp)
512 {
513         size_t           sz, bufsz, spsz;
514         const char      *str;
515
516         /* 
517          * Calculate our width and use the spacing, with a minimum
518          * spacing dictated by position (centre, e.g,. gets a space on
519          * either side, while right/left get a single adjacent space).
520          */
521
522         bufsz = spsz = 0;
523         str = dp->string ? dp->string : "";
524         sz = (*tbl->slen)(str, tbl->arg);
525
526         /* FIXME: TBL_DATA_HORIZ et al.? */
527
528         assert(dp->layout);
529         switch (dp->layout->pos) {
530         case (TBL_CELL_LONG):
531                 /* FALLTHROUGH */
532         case (TBL_CELL_CENTRE):
533                 bufsz = (*tbl->len)(1, tbl->arg);
534                 break;
535         default:
536                 bufsz = (*tbl->len)(1, tbl->arg);
537                 break;
538         }
539
540         if (dp->layout->spacing) {
541                 spsz = (*tbl->len)(dp->layout->spacing, tbl->arg);
542                 bufsz = bufsz > spsz ? bufsz : spsz;
543         }
544
545         sz += bufsz;
546         if (col->width < sz)
547                 col->width = sz;
548 }
549
550 static void
551 tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
552                 const struct tbl *tp, const struct tbl_dat *dp)
553 {
554         int              i;
555         size_t           sz, psz, ssz, d;
556         const char      *str;
557         char            *cp;
558         char             buf[2];
559
560         /*
561          * First calculate number width and decimal place (last + 1 for
562          * no-decimal numbers).  If the stored decimal is subsequent
563          * ours, make our size longer by that difference
564          * (right-"shifting"); similarly, if ours is subsequent the
565          * stored, then extend the stored size by the difference.
566          * Finally, re-assign the stored values.
567          */
568
569         str = dp->string ? dp->string : "";
570         sz = (*tbl->slen)(str, tbl->arg);
571
572         /* FIXME: TBL_DATA_HORIZ et al.? */
573
574         buf[0] = tp->decimal;
575         buf[1] = '\0';
576
577         psz = (*tbl->slen)(buf, tbl->arg);
578
579         if (NULL != (cp = strrchr(str, tp->decimal))) {
580                 buf[1] = '\0';
581                 for (ssz = 0, i = 0; cp != &str[i]; i++) {
582                         buf[0] = str[i];
583                         ssz += (*tbl->slen)(buf, tbl->arg);
584                 }
585                 d = ssz + psz;
586         } else
587                 d = sz + psz;
588
589         /* Padding. */
590
591         sz += (*tbl->len)(2, tbl->arg);
592         d += (*tbl->len)(1, tbl->arg);
593
594         /* Adjust the settings for this column. */
595
596         if (col->decimal > d) {
597                 sz += col->decimal - d;
598                 d = col->decimal;
599         } else
600                 col->width += d - col->decimal;
601
602         if (sz > col->width)
603                 col->width = sz;
604         if (d > col->decimal)
605                 col->decimal = d;
606
607         /* Adjust for stipulated width. */
608
609         if (col->width < dp->layout->spacing)
610                 col->width = dp->layout->spacing;
611 }
612
613