Import mandoc-1.14.6.
[dragonfly.git] / contrib / mdocml / out.c
1 /*      $Id: out.c,v 1.82 2021/09/07 17:07:58 schwarze Exp $ */
2 /*
3  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2011, 2014, 2015, 2017, 2018, 2019, 2021
5  *               Ingo Schwarze <schwarze@openbsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 #include "config.h"
20
21 #include <sys/types.h>
22
23 #include <assert.h>
24 #include <ctype.h>
25 #include <stdint.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30
31 #include "mandoc_aux.h"
32 #include "mandoc.h"
33 #include "tbl.h"
34 #include "out.h"
35
36 struct  tbl_colgroup {
37         struct tbl_colgroup     *next;
38         size_t                   wanted;
39         int                      startcol;
40         int                      endcol;
41 };
42
43 static  size_t  tblcalc_data(struct rofftbl *, struct roffcol *,
44                         const struct tbl_opts *, const struct tbl_dat *,
45                         size_t);
46 static  size_t  tblcalc_literal(struct rofftbl *, struct roffcol *,
47                         const struct tbl_dat *, size_t);
48 static  size_t  tblcalc_number(struct rofftbl *, struct roffcol *,
49                         const struct tbl_opts *, const struct tbl_dat *);
50
51
52 /*
53  * Parse the *src string and store a scaling unit into *dst.
54  * If the string doesn't specify the unit, use the default.
55  * If no default is specified, fail.
56  * Return a pointer to the byte after the last byte used,
57  * or NULL on total failure.
58  */
59 const char *
60 a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
61 {
62         char            *endptr;
63
64         dst->unit = def == SCALE_MAX ? SCALE_BU : def;
65         dst->scale = strtod(src, &endptr);
66         if (endptr == src)
67                 return NULL;
68
69         switch (*endptr++) {
70         case 'c':
71                 dst->unit = SCALE_CM;
72                 break;
73         case 'i':
74                 dst->unit = SCALE_IN;
75                 break;
76         case 'f':
77                 dst->unit = SCALE_FS;
78                 break;
79         case 'M':
80                 dst->unit = SCALE_MM;
81                 break;
82         case 'm':
83                 dst->unit = SCALE_EM;
84                 break;
85         case 'n':
86                 dst->unit = SCALE_EN;
87                 break;
88         case 'P':
89                 dst->unit = SCALE_PC;
90                 break;
91         case 'p':
92                 dst->unit = SCALE_PT;
93                 break;
94         case 'u':
95                 dst->unit = SCALE_BU;
96                 break;
97         case 'v':
98                 dst->unit = SCALE_VS;
99                 break;
100         default:
101                 endptr--;
102                 if (SCALE_MAX == def)
103                         return NULL;
104                 dst->unit = def;
105                 break;
106         }
107         return endptr;
108 }
109
110 /*
111  * Calculate the abstract widths and decimal positions of columns in a
112  * table.  This routine allocates the columns structures then runs over
113  * all rows and cells in the table.  The function pointers in "tbl" are
114  * used for the actual width calculations.
115  */
116 void
117 tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first,
118     size_t offset, size_t rmargin)
119 {
120         struct roffsu            su;
121         const struct tbl_opts   *opts;
122         const struct tbl_span   *sp;
123         const struct tbl_dat    *dp;
124         struct roffcol          *col;
125         struct tbl_colgroup     *first_group, **gp, *g;
126         size_t                   ewidth, min1, min2, wanted, width, xwidth;
127         int                      done, icol, maxcol, necol, nxcol, quirkcol;
128
129         /*
130          * Allocate the master column specifiers.  These will hold the
131          * widths and decimal positions for all cells in the column.  It
132          * must be freed and nullified by the caller.
133          */
134
135         assert(tbl->cols == NULL);
136         tbl->cols = mandoc_calloc((size_t)sp_first->opts->cols,
137             sizeof(struct roffcol));
138         opts = sp_first->opts;
139
140         maxcol = -1;
141         first_group = NULL;
142         for (sp = sp_first; sp != NULL; sp = sp->next) {
143                 if (sp->pos != TBL_SPAN_DATA)
144                         continue;
145
146                 /*
147                  * Account for the data cells in the layout, matching it
148                  * to data cells in the data section.
149                  */
150
151                 gp = &first_group;
152                 for (dp = sp->first; dp != NULL; dp = dp->next) {
153                         icol = dp->layout->col;
154                         while (maxcol < icol + dp->hspans)
155                                 tbl->cols[++maxcol].spacing = SIZE_MAX;
156                         col = tbl->cols + icol;
157                         col->flags |= dp->layout->flags;
158                         if (dp->layout->flags & TBL_CELL_WIGN)
159                                 continue;
160
161                         /* Handle explicit width specifications. */
162
163                         if (dp->layout->wstr != NULL &&
164                             dp->layout->width == 0 &&
165                             a2roffsu(dp->layout->wstr, &su, SCALE_EN)
166                             != NULL)
167                                 dp->layout->width =
168                                     (*tbl->sulen)(&su, tbl->arg);
169                         if (col->width < dp->layout->width)
170                                 col->width = dp->layout->width;
171                         if (dp->layout->spacing != SIZE_MAX &&
172                             (col->spacing == SIZE_MAX ||
173                              col->spacing < dp->layout->spacing))
174                                 col->spacing = dp->layout->spacing;
175
176                         /*
177                          * Calculate an automatic width.
178                          * Except for spanning cells, apply it.
179                          */
180
181                         width = tblcalc_data(tbl,
182                             dp->hspans == 0 ? col : NULL,
183                             opts, dp,
184                             dp->block == 0 ? 0 :
185                             dp->layout->width ? dp->layout->width :
186                             rmargin ? (rmargin + sp->opts->cols / 2)
187                             / (sp->opts->cols + 1) : 0);
188                         if (dp->hspans == 0)
189                                 continue;
190
191                         /*
192                          * Build an ordered, singly linked list
193                          * of all groups of columns joined by spans,
194                          * recording the minimum width for each group.
195                          */
196
197                         while (*gp != NULL && ((*gp)->startcol < icol ||
198                             (*gp)->endcol < icol + dp->hspans))
199                                 gp = &(*gp)->next;
200                         if (*gp == NULL || (*gp)->startcol > icol ||
201                             (*gp)->endcol > icol + dp->hspans) {
202                                 g = mandoc_malloc(sizeof(*g));
203                                 g->next = *gp;
204                                 g->wanted = width;
205                                 g->startcol = icol;
206                                 g->endcol = icol + dp->hspans;
207                                 *gp = g;
208                         } else if ((*gp)->wanted < width)
209                                 (*gp)->wanted = width;
210                 }
211         }
212
213         /*
214          * The minimum width of columns explicitly specified
215          * in the layout is 1n.
216          */
217
218         if (maxcol < sp_first->opts->cols - 1)
219                 maxcol = sp_first->opts->cols - 1;
220         for (icol = 0; icol <= maxcol; icol++) {
221                 col = tbl->cols + icol;
222                 if (col->width < 1)
223                         col->width = 1;
224
225                 /*
226                  * Column spacings are needed for span width
227                  * calculations, so set the default values now.
228                  */
229
230                 if (col->spacing == SIZE_MAX || icol == maxcol)
231                         col->spacing = 3;
232         }
233
234         /*
235          * Replace the minimum widths with the missing widths,
236          * and dismiss groups that are already wide enough.
237          */
238
239         gp = &first_group;
240         while ((g = *gp) != NULL) {
241                 done = 0;
242                 for (icol = g->startcol; icol <= g->endcol; icol++) {
243                         width = tbl->cols[icol].width;
244                         if (icol < g->endcol)
245                                 width += tbl->cols[icol].spacing;
246                         if (g->wanted <= width) {
247                                 done = 1;
248                                 break;
249                         } else
250                                 (*gp)->wanted -= width;
251                 }
252                 if (done) {
253                         *gp = g->next;
254                         free(g);
255                 } else
256                         gp = &(*gp)->next;
257         }
258
259         while (first_group != NULL) {
260
261                 /*
262                  * Find the smallest and second smallest column width
263                  * among the columns which may need expamsion.
264                  */
265
266                 min1 = min2 = SIZE_MAX;
267                 for (icol = 0; icol <= maxcol; icol++) {
268                         width = tbl->cols[icol].width;
269                         if (min1 > width) {
270                                 min2 = min1;
271                                 min1 = width;
272                         } else if (min1 < width && min2 > width)
273                                 min2 = width;
274                 }
275
276                 /*
277                  * Find the minimum wanted width
278                  * for any one of the narrowest columns,
279                  * and mark the columns wanting that width.
280                  */
281
282                 wanted = min2;
283                 for (g = first_group; g != NULL; g = g->next) {
284                         necol = 0;
285                         for (icol = g->startcol; icol <= g->endcol; icol++)
286                                 if (tbl->cols[icol].width == min1)
287                                         necol++;
288                         if (necol == 0)
289                                 continue;
290                         width = min1 + (g->wanted - 1) / necol + 1;
291                         if (width > min2)
292                                 width = min2;
293                         if (wanted > width)
294                                 wanted = width;
295                 }
296
297                 /* Record the effect of the widening. */
298
299                 gp = &first_group;
300                 while ((g = *gp) != NULL) {
301                         done = 0;
302                         for (icol = g->startcol; icol <= g->endcol; icol++) {
303                                 if (tbl->cols[icol].width != min1)
304                                         continue;
305                                 if (g->wanted <= wanted - min1) {
306                                         tbl->cols[icol].width += g->wanted;
307                                         done = 1;
308                                         break;
309                                 }
310                                 tbl->cols[icol].width = wanted;
311                                 g->wanted -= wanted - min1;
312                         }
313                         if (done) {
314                                 *gp = g->next;
315                                 free(g);
316                         } else
317                                 gp = &(*gp)->next;
318                 }
319         }
320
321         /*
322          * Align numbers with text.
323          * Count columns to equalize and columns to maximize.
324          * Find maximum width of the columns to equalize.
325          * Find total width of the columns *not* to maximize.
326          */
327
328         necol = nxcol = 0;
329         ewidth = xwidth = 0;
330         for (icol = 0; icol <= maxcol; icol++) {
331                 col = tbl->cols + icol;
332                 if (col->width > col->nwidth)
333                         col->decimal += (col->width - col->nwidth) / 2;
334                 if (col->flags & TBL_CELL_EQUAL) {
335                         necol++;
336                         if (ewidth < col->width)
337                                 ewidth = col->width;
338                 }
339                 if (col->flags & TBL_CELL_WMAX)
340                         nxcol++;
341                 else
342                         xwidth += col->width;
343         }
344
345         /*
346          * Equalize columns, if requested for any of them.
347          * Update total width of the columns not to maximize.
348          */
349
350         if (necol) {
351                 for (icol = 0; icol <= maxcol; icol++) {
352                         col = tbl->cols + icol;
353                         if ( ! (col->flags & TBL_CELL_EQUAL))
354                                 continue;
355                         if (col->width == ewidth)
356                                 continue;
357                         if (nxcol && rmargin)
358                                 xwidth += ewidth - col->width;
359                         col->width = ewidth;
360                 }
361         }
362
363         /*
364          * If there are any columns to maximize, find the total
365          * available width, deducting 3n margins between columns.
366          * Distribute the available width evenly.
367          */
368
369         if (nxcol && rmargin) {
370                 xwidth += 3*maxcol +
371                     (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ?
372                      2 : !!opts->lvert + !!opts->rvert);
373                 if (rmargin <= offset + xwidth)
374                         return;
375                 xwidth = rmargin - offset - xwidth;
376
377                 /*
378                  * Emulate a bug in GNU tbl width calculation that
379                  * manifests itself for large numbers of x-columns.
380                  * Emulating it for 5 x-columns gives identical
381                  * behaviour for up to 6 x-columns.
382                  */
383
384                 if (nxcol == 5) {
385                         quirkcol = xwidth % nxcol + 2;
386                         if (quirkcol != 3 && quirkcol != 4)
387                                 quirkcol = -1;
388                 } else
389                         quirkcol = -1;
390
391                 necol = 0;
392                 ewidth = 0;
393                 for (icol = 0; icol <= maxcol; icol++) {
394                         col = tbl->cols + icol;
395                         if ( ! (col->flags & TBL_CELL_WMAX))
396                                 continue;
397                         col->width = (double)xwidth * ++necol / nxcol
398                             - ewidth + 0.4995;
399                         if (necol == quirkcol)
400                                 col->width--;
401                         ewidth += col->width;
402                 }
403         }
404 }
405
406 static size_t
407 tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
408     const struct tbl_opts *opts, const struct tbl_dat *dp, size_t mw)
409 {
410         size_t           sz;
411
412         /* Branch down into data sub-types. */
413
414         switch (dp->layout->pos) {
415         case TBL_CELL_HORIZ:
416         case TBL_CELL_DHORIZ:
417                 sz = (*tbl->len)(1, tbl->arg);
418                 if (col != NULL && col->width < sz)
419                         col->width = sz;
420                 return sz;
421         case TBL_CELL_LONG:
422         case TBL_CELL_CENTRE:
423         case TBL_CELL_LEFT:
424         case TBL_CELL_RIGHT:
425                 return tblcalc_literal(tbl, col, dp, mw);
426         case TBL_CELL_NUMBER:
427                 return tblcalc_number(tbl, col, opts, dp);
428         case TBL_CELL_DOWN:
429                 return 0;
430         default:
431                 abort();
432         }
433 }
434
435 static size_t
436 tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
437     const struct tbl_dat *dp, size_t mw)
438 {
439         const char      *str;   /* Beginning of the first line. */
440         const char      *beg;   /* Beginning of the current line. */
441         char            *end;   /* End of the current line. */
442         size_t           lsz;   /* Length of the current line. */
443         size_t           wsz;   /* Length of the current word. */
444         size_t           msz;   /* Length of the longest line. */
445
446         if (dp->string == NULL || *dp->string == '\0')
447                 return 0;
448         str = mw ? mandoc_strdup(dp->string) : dp->string;
449         msz = lsz = 0;
450         for (beg = str; beg != NULL && *beg != '\0'; beg = end) {
451                 end = mw ? strchr(beg, ' ') : NULL;
452                 if (end != NULL) {
453                         *end++ = '\0';
454                         while (*end == ' ')
455                                 end++;
456                 }
457                 wsz = (*tbl->slen)(beg, tbl->arg);
458                 if (mw && lsz && lsz + 1 + wsz <= mw)
459                         lsz += 1 + wsz;
460                 else
461                         lsz = wsz;
462                 if (msz < lsz)
463                         msz = lsz;
464         }
465         if (mw)
466                 free((void *)str);
467         if (col != NULL && col->width < msz)
468                 col->width = msz;
469         return msz;
470 }
471
472 static size_t
473 tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
474                 const struct tbl_opts *opts, const struct tbl_dat *dp)
475 {
476         const char      *cp, *lastdigit, *lastpoint;
477         size_t           intsz, totsz;
478         char             buf[2];
479
480         if (dp->string == NULL || *dp->string == '\0')
481                 return 0;
482
483         totsz = (*tbl->slen)(dp->string, tbl->arg);
484         if (col == NULL)
485                 return totsz;
486
487         /*
488          * Find the last digit and
489          * the last decimal point that is adjacent to a digit.
490          * The alignment indicator "\&" overrides everything.
491          */
492
493         lastdigit = lastpoint = NULL;
494         for (cp = dp->string; cp[0] != '\0'; cp++) {
495                 if (cp[0] == '\\' && cp[1] == '&') {
496                         lastdigit = lastpoint = cp;
497                         break;
498                 } else if (cp[0] == opts->decimal &&
499                     (isdigit((unsigned char)cp[1]) ||
500                      (cp > dp->string && isdigit((unsigned char)cp[-1]))))
501                         lastpoint = cp;
502                 else if (isdigit((unsigned char)cp[0]))
503                         lastdigit = cp;
504         }
505
506         /* Not a number, treat as a literal string. */
507
508         if (lastdigit == NULL) {
509                 if (col != NULL && col->width < totsz)
510                         col->width = totsz;
511                 return totsz;
512         }
513
514         /* Measure the width of the integer part. */
515
516         if (lastpoint == NULL)
517                 lastpoint = lastdigit + 1;
518         intsz = 0;
519         buf[1] = '\0';
520         for (cp = dp->string; cp < lastpoint; cp++) {
521                 buf[0] = cp[0];
522                 intsz += (*tbl->slen)(buf, tbl->arg);
523         }
524
525         /*
526          * If this number has more integer digits than all numbers
527          * seen on earlier lines, shift them all to the right.
528          * If it has fewer, shift this number to the right.
529          */
530
531         if (intsz > col->decimal) {
532                 col->nwidth += intsz - col->decimal;
533                 col->decimal = intsz;
534         } else
535                 totsz += col->decimal - intsz;
536
537         /* Update the maximum total width seen so far. */
538
539         if (totsz > col->nwidth)
540                 col->nwidth = totsz;
541         if (col->nwidth > col->width)
542                 col->width = col->nwidth;
543         return totsz;
544 }