1*c1c95addSBrooks Davis /* $Id: out.c,v 1.85 2021/10/17 21:05:54 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
361d06d6bSBaptiste Daroussin * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
46d38604fSBaptiste Daroussin * Copyright (c) 2011, 2014, 2015, 2017, 2018, 2019, 2021
56d38604fSBaptiste Daroussin * Ingo Schwarze <schwarze@openbsd.org>
661d06d6bSBaptiste Daroussin *
761d06d6bSBaptiste Daroussin * Permission to use, copy, modify, and distribute this software for any
861d06d6bSBaptiste Daroussin * purpose with or without fee is hereby granted, provided that the above
961d06d6bSBaptiste Daroussin * copyright notice and this permission notice appear in all copies.
1061d06d6bSBaptiste Daroussin *
1161d06d6bSBaptiste Daroussin * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1261d06d6bSBaptiste Daroussin * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1361d06d6bSBaptiste Daroussin * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1461d06d6bSBaptiste Daroussin * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1561d06d6bSBaptiste Daroussin * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1661d06d6bSBaptiste Daroussin * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1761d06d6bSBaptiste Daroussin * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1861d06d6bSBaptiste Daroussin */
1961d06d6bSBaptiste Daroussin #include "config.h"
2061d06d6bSBaptiste Daroussin
2161d06d6bSBaptiste Daroussin #include <sys/types.h>
2261d06d6bSBaptiste Daroussin
2361d06d6bSBaptiste Daroussin #include <assert.h>
247295610fSBaptiste Daroussin #include <ctype.h>
2561d06d6bSBaptiste Daroussin #include <stdint.h>
266d38604fSBaptiste Daroussin #include <stdio.h>
2761d06d6bSBaptiste Daroussin #include <stdlib.h>
2861d06d6bSBaptiste Daroussin #include <string.h>
2961d06d6bSBaptiste Daroussin #include <time.h>
3061d06d6bSBaptiste Daroussin
3161d06d6bSBaptiste Daroussin #include "mandoc_aux.h"
326d38604fSBaptiste Daroussin #include "mandoc.h"
337295610fSBaptiste Daroussin #include "tbl.h"
3461d06d6bSBaptiste Daroussin #include "out.h"
3561d06d6bSBaptiste Daroussin
367295610fSBaptiste Daroussin struct tbl_colgroup {
377295610fSBaptiste Daroussin struct tbl_colgroup *next;
387295610fSBaptiste Daroussin size_t wanted;
397295610fSBaptiste Daroussin int startcol;
407295610fSBaptiste Daroussin int endcol;
417295610fSBaptiste Daroussin };
427295610fSBaptiste Daroussin
437295610fSBaptiste Daroussin static size_t tblcalc_data(struct rofftbl *, struct roffcol *,
4461d06d6bSBaptiste Daroussin const struct tbl_opts *, const struct tbl_dat *,
4561d06d6bSBaptiste Daroussin size_t);
467295610fSBaptiste Daroussin static size_t tblcalc_literal(struct rofftbl *, struct roffcol *,
4761d06d6bSBaptiste Daroussin const struct tbl_dat *, size_t);
487295610fSBaptiste Daroussin static size_t tblcalc_number(struct rofftbl *, struct roffcol *,
4961d06d6bSBaptiste Daroussin const struct tbl_opts *, const struct tbl_dat *);
5061d06d6bSBaptiste Daroussin
5161d06d6bSBaptiste Daroussin
5261d06d6bSBaptiste Daroussin /*
5361d06d6bSBaptiste Daroussin * Parse the *src string and store a scaling unit into *dst.
5461d06d6bSBaptiste Daroussin * If the string doesn't specify the unit, use the default.
5561d06d6bSBaptiste Daroussin * If no default is specified, fail.
5661d06d6bSBaptiste Daroussin * Return a pointer to the byte after the last byte used,
5761d06d6bSBaptiste Daroussin * or NULL on total failure.
5861d06d6bSBaptiste Daroussin */
5961d06d6bSBaptiste Daroussin const char *
a2roffsu(const char * src,struct roffsu * dst,enum roffscale def)6061d06d6bSBaptiste Daroussin a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
6161d06d6bSBaptiste Daroussin {
6261d06d6bSBaptiste Daroussin char *endptr;
6361d06d6bSBaptiste Daroussin
6461d06d6bSBaptiste Daroussin dst->unit = def == SCALE_MAX ? SCALE_BU : def;
6561d06d6bSBaptiste Daroussin dst->scale = strtod(src, &endptr);
6661d06d6bSBaptiste Daroussin if (endptr == src)
6761d06d6bSBaptiste Daroussin return NULL;
6861d06d6bSBaptiste Daroussin
6961d06d6bSBaptiste Daroussin switch (*endptr++) {
7061d06d6bSBaptiste Daroussin case 'c':
7161d06d6bSBaptiste Daroussin dst->unit = SCALE_CM;
7261d06d6bSBaptiste Daroussin break;
7361d06d6bSBaptiste Daroussin case 'i':
7461d06d6bSBaptiste Daroussin dst->unit = SCALE_IN;
7561d06d6bSBaptiste Daroussin break;
7661d06d6bSBaptiste Daroussin case 'f':
7761d06d6bSBaptiste Daroussin dst->unit = SCALE_FS;
7861d06d6bSBaptiste Daroussin break;
7961d06d6bSBaptiste Daroussin case 'M':
8061d06d6bSBaptiste Daroussin dst->unit = SCALE_MM;
8161d06d6bSBaptiste Daroussin break;
8261d06d6bSBaptiste Daroussin case 'm':
8361d06d6bSBaptiste Daroussin dst->unit = SCALE_EM;
8461d06d6bSBaptiste Daroussin break;
8561d06d6bSBaptiste Daroussin case 'n':
8661d06d6bSBaptiste Daroussin dst->unit = SCALE_EN;
8761d06d6bSBaptiste Daroussin break;
8861d06d6bSBaptiste Daroussin case 'P':
8961d06d6bSBaptiste Daroussin dst->unit = SCALE_PC;
9061d06d6bSBaptiste Daroussin break;
9161d06d6bSBaptiste Daroussin case 'p':
9261d06d6bSBaptiste Daroussin dst->unit = SCALE_PT;
9361d06d6bSBaptiste Daroussin break;
9461d06d6bSBaptiste Daroussin case 'u':
9561d06d6bSBaptiste Daroussin dst->unit = SCALE_BU;
9661d06d6bSBaptiste Daroussin break;
9761d06d6bSBaptiste Daroussin case 'v':
9861d06d6bSBaptiste Daroussin dst->unit = SCALE_VS;
9961d06d6bSBaptiste Daroussin break;
10061d06d6bSBaptiste Daroussin default:
10161d06d6bSBaptiste Daroussin endptr--;
10261d06d6bSBaptiste Daroussin if (SCALE_MAX == def)
10361d06d6bSBaptiste Daroussin return NULL;
10461d06d6bSBaptiste Daroussin dst->unit = def;
10561d06d6bSBaptiste Daroussin break;
10661d06d6bSBaptiste Daroussin }
10761d06d6bSBaptiste Daroussin return endptr;
10861d06d6bSBaptiste Daroussin }
10961d06d6bSBaptiste Daroussin
11061d06d6bSBaptiste Daroussin /*
11161d06d6bSBaptiste Daroussin * Calculate the abstract widths and decimal positions of columns in a
11261d06d6bSBaptiste Daroussin * table. This routine allocates the columns structures then runs over
11361d06d6bSBaptiste Daroussin * all rows and cells in the table. The function pointers in "tbl" are
11461d06d6bSBaptiste Daroussin * used for the actual width calculations.
11561d06d6bSBaptiste Daroussin */
11661d06d6bSBaptiste Daroussin void
tblcalc(struct rofftbl * tbl,const struct tbl_span * sp_first,size_t offset,size_t rmargin)1177295610fSBaptiste Daroussin tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first,
11861d06d6bSBaptiste Daroussin size_t offset, size_t rmargin)
11961d06d6bSBaptiste Daroussin {
12061d06d6bSBaptiste Daroussin struct roffsu su;
12161d06d6bSBaptiste Daroussin const struct tbl_opts *opts;
1227295610fSBaptiste Daroussin const struct tbl_span *sp;
12361d06d6bSBaptiste Daroussin const struct tbl_dat *dp;
12461d06d6bSBaptiste Daroussin struct roffcol *col;
1257295610fSBaptiste Daroussin struct tbl_colgroup *first_group, **gp, *g;
126*c1c95addSBrooks Davis size_t *colwidth;
1277295610fSBaptiste Daroussin size_t ewidth, min1, min2, wanted, width, xwidth;
1287295610fSBaptiste Daroussin int done, icol, maxcol, necol, nxcol, quirkcol;
12961d06d6bSBaptiste Daroussin
13061d06d6bSBaptiste Daroussin /*
13161d06d6bSBaptiste Daroussin * Allocate the master column specifiers. These will hold the
13261d06d6bSBaptiste Daroussin * widths and decimal positions for all cells in the column. It
13361d06d6bSBaptiste Daroussin * must be freed and nullified by the caller.
13461d06d6bSBaptiste Daroussin */
13561d06d6bSBaptiste Daroussin
1367295610fSBaptiste Daroussin assert(tbl->cols == NULL);
1377295610fSBaptiste Daroussin tbl->cols = mandoc_calloc((size_t)sp_first->opts->cols,
13861d06d6bSBaptiste Daroussin sizeof(struct roffcol));
1397295610fSBaptiste Daroussin opts = sp_first->opts;
14061d06d6bSBaptiste Daroussin
1417295610fSBaptiste Daroussin maxcol = -1;
1427295610fSBaptiste Daroussin first_group = NULL;
1437295610fSBaptiste Daroussin for (sp = sp_first; sp != NULL; sp = sp->next) {
1447295610fSBaptiste Daroussin if (sp->pos != TBL_SPAN_DATA)
14561d06d6bSBaptiste Daroussin continue;
1467295610fSBaptiste Daroussin
14761d06d6bSBaptiste Daroussin /*
14861d06d6bSBaptiste Daroussin * Account for the data cells in the layout, matching it
14961d06d6bSBaptiste Daroussin * to data cells in the data section.
15061d06d6bSBaptiste Daroussin */
1517295610fSBaptiste Daroussin
1527295610fSBaptiste Daroussin for (dp = sp->first; dp != NULL; dp = dp->next) {
15361d06d6bSBaptiste Daroussin icol = dp->layout->col;
15445a5aec3SBaptiste Daroussin while (maxcol < icol + dp->hspans)
15561d06d6bSBaptiste Daroussin tbl->cols[++maxcol].spacing = SIZE_MAX;
15661d06d6bSBaptiste Daroussin col = tbl->cols + icol;
15761d06d6bSBaptiste Daroussin col->flags |= dp->layout->flags;
15861d06d6bSBaptiste Daroussin if (dp->layout->flags & TBL_CELL_WIGN)
15961d06d6bSBaptiste Daroussin continue;
1607295610fSBaptiste Daroussin
1617295610fSBaptiste Daroussin /* Handle explicit width specifications. */
1627295610fSBaptiste Daroussin
16361d06d6bSBaptiste Daroussin if (dp->layout->wstr != NULL &&
16461d06d6bSBaptiste Daroussin dp->layout->width == 0 &&
16561d06d6bSBaptiste Daroussin a2roffsu(dp->layout->wstr, &su, SCALE_EN)
16661d06d6bSBaptiste Daroussin != NULL)
16761d06d6bSBaptiste Daroussin dp->layout->width =
16861d06d6bSBaptiste Daroussin (*tbl->sulen)(&su, tbl->arg);
16961d06d6bSBaptiste Daroussin if (col->width < dp->layout->width)
17061d06d6bSBaptiste Daroussin col->width = dp->layout->width;
17161d06d6bSBaptiste Daroussin if (dp->layout->spacing != SIZE_MAX &&
17261d06d6bSBaptiste Daroussin (col->spacing == SIZE_MAX ||
17361d06d6bSBaptiste Daroussin col->spacing < dp->layout->spacing))
17461d06d6bSBaptiste Daroussin col->spacing = dp->layout->spacing;
1757295610fSBaptiste Daroussin
1767295610fSBaptiste Daroussin /*
1777295610fSBaptiste Daroussin * Calculate an automatic width.
1787295610fSBaptiste Daroussin * Except for spanning cells, apply it.
1797295610fSBaptiste Daroussin */
1807295610fSBaptiste Daroussin
1817295610fSBaptiste Daroussin width = tblcalc_data(tbl,
1827295610fSBaptiste Daroussin dp->hspans == 0 ? col : NULL,
1837295610fSBaptiste Daroussin opts, dp,
18461d06d6bSBaptiste Daroussin dp->block == 0 ? 0 :
18561d06d6bSBaptiste Daroussin dp->layout->width ? dp->layout->width :
18661d06d6bSBaptiste Daroussin rmargin ? (rmargin + sp->opts->cols / 2)
18761d06d6bSBaptiste Daroussin / (sp->opts->cols + 1) : 0);
1887295610fSBaptiste Daroussin if (dp->hspans == 0)
1897295610fSBaptiste Daroussin continue;
1907295610fSBaptiste Daroussin
1917295610fSBaptiste Daroussin /*
192*c1c95addSBrooks Davis * Build a singly linked list
1937295610fSBaptiste Daroussin * of all groups of columns joined by spans,
1947295610fSBaptiste Daroussin * recording the minimum width for each group.
1957295610fSBaptiste Daroussin */
1967295610fSBaptiste Daroussin
197*c1c95addSBrooks Davis gp = &first_group;
198*c1c95addSBrooks Davis while (*gp != NULL && ((*gp)->startcol != icol ||
199*c1c95addSBrooks Davis (*gp)->endcol != icol + dp->hspans))
2007295610fSBaptiste Daroussin gp = &(*gp)->next;
201*c1c95addSBrooks Davis if (*gp == NULL) {
2027295610fSBaptiste Daroussin g = mandoc_malloc(sizeof(*g));
2037295610fSBaptiste Daroussin g->next = *gp;
2047295610fSBaptiste Daroussin g->wanted = width;
2057295610fSBaptiste Daroussin g->startcol = icol;
2067295610fSBaptiste Daroussin g->endcol = icol + dp->hspans;
2077295610fSBaptiste Daroussin *gp = g;
2087295610fSBaptiste Daroussin } else if ((*gp)->wanted < width)
2097295610fSBaptiste Daroussin (*gp)->wanted = width;
21061d06d6bSBaptiste Daroussin }
21161d06d6bSBaptiste Daroussin }
21261d06d6bSBaptiste Daroussin
21361d06d6bSBaptiste Daroussin /*
2146d38604fSBaptiste Daroussin * The minimum width of columns explicitly specified
2156d38604fSBaptiste Daroussin * in the layout is 1n.
2167295610fSBaptiste Daroussin */
2177295610fSBaptiste Daroussin
2186d38604fSBaptiste Daroussin if (maxcol < sp_first->opts->cols - 1)
2196d38604fSBaptiste Daroussin maxcol = sp_first->opts->cols - 1;
2206d38604fSBaptiste Daroussin for (icol = 0; icol <= maxcol; icol++) {
2216d38604fSBaptiste Daroussin col = tbl->cols + icol;
2226d38604fSBaptiste Daroussin if (col->width < 1)
2236d38604fSBaptiste Daroussin col->width = 1;
2246d38604fSBaptiste Daroussin
2256d38604fSBaptiste Daroussin /*
2266d38604fSBaptiste Daroussin * Column spacings are needed for span width
2276d38604fSBaptiste Daroussin * calculations, so set the default values now.
2286d38604fSBaptiste Daroussin */
2296d38604fSBaptiste Daroussin
2306d38604fSBaptiste Daroussin if (col->spacing == SIZE_MAX || icol == maxcol)
2316d38604fSBaptiste Daroussin col->spacing = 3;
2326d38604fSBaptiste Daroussin }
2337295610fSBaptiste Daroussin
2347295610fSBaptiste Daroussin /*
2357295610fSBaptiste Daroussin * Replace the minimum widths with the missing widths,
2367295610fSBaptiste Daroussin * and dismiss groups that are already wide enough.
2377295610fSBaptiste Daroussin */
2387295610fSBaptiste Daroussin
2397295610fSBaptiste Daroussin gp = &first_group;
2407295610fSBaptiste Daroussin while ((g = *gp) != NULL) {
2417295610fSBaptiste Daroussin done = 0;
2427295610fSBaptiste Daroussin for (icol = g->startcol; icol <= g->endcol; icol++) {
2437295610fSBaptiste Daroussin width = tbl->cols[icol].width;
2447295610fSBaptiste Daroussin if (icol < g->endcol)
2457295610fSBaptiste Daroussin width += tbl->cols[icol].spacing;
2467295610fSBaptiste Daroussin if (g->wanted <= width) {
2477295610fSBaptiste Daroussin done = 1;
2487295610fSBaptiste Daroussin break;
2497295610fSBaptiste Daroussin } else
250*c1c95addSBrooks Davis g->wanted -= width;
2517295610fSBaptiste Daroussin }
2527295610fSBaptiste Daroussin if (done) {
2537295610fSBaptiste Daroussin *gp = g->next;
2547295610fSBaptiste Daroussin free(g);
2557295610fSBaptiste Daroussin } else
256*c1c95addSBrooks Davis gp = &g->next;
2577295610fSBaptiste Daroussin }
2587295610fSBaptiste Daroussin
259*c1c95addSBrooks Davis colwidth = mandoc_reallocarray(NULL, maxcol + 1, sizeof(*colwidth));
2607295610fSBaptiste Daroussin while (first_group != NULL) {
2617295610fSBaptiste Daroussin
2627295610fSBaptiste Daroussin /*
263*c1c95addSBrooks Davis * Rebuild the array of the widths of all columns
264*c1c95addSBrooks Davis * participating in spans that require expansion.
265*c1c95addSBrooks Davis */
266*c1c95addSBrooks Davis
267*c1c95addSBrooks Davis for (icol = 0; icol <= maxcol; icol++)
268*c1c95addSBrooks Davis colwidth[icol] = SIZE_MAX;
269*c1c95addSBrooks Davis for (g = first_group; g != NULL; g = g->next)
270*c1c95addSBrooks Davis for (icol = g->startcol; icol <= g->endcol; icol++)
271*c1c95addSBrooks Davis colwidth[icol] = tbl->cols[icol].width;
272*c1c95addSBrooks Davis
273*c1c95addSBrooks Davis /*
2747295610fSBaptiste Daroussin * Find the smallest and second smallest column width
2757295610fSBaptiste Daroussin * among the columns which may need expamsion.
2767295610fSBaptiste Daroussin */
2777295610fSBaptiste Daroussin
2787295610fSBaptiste Daroussin min1 = min2 = SIZE_MAX;
2797295610fSBaptiste Daroussin for (icol = 0; icol <= maxcol; icol++) {
280*c1c95addSBrooks Davis width = colwidth[icol];
2816d38604fSBaptiste Daroussin if (min1 > width) {
2827295610fSBaptiste Daroussin min2 = min1;
2836d38604fSBaptiste Daroussin min1 = width;
2846d38604fSBaptiste Daroussin } else if (min1 < width && min2 > width)
2856d38604fSBaptiste Daroussin min2 = width;
2867295610fSBaptiste Daroussin }
2877295610fSBaptiste Daroussin
2887295610fSBaptiste Daroussin /*
2897295610fSBaptiste Daroussin * Find the minimum wanted width
2907295610fSBaptiste Daroussin * for any one of the narrowest columns,
2917295610fSBaptiste Daroussin * and mark the columns wanting that width.
2927295610fSBaptiste Daroussin */
2937295610fSBaptiste Daroussin
2947295610fSBaptiste Daroussin wanted = min2;
2957295610fSBaptiste Daroussin for (g = first_group; g != NULL; g = g->next) {
2967295610fSBaptiste Daroussin necol = 0;
2977295610fSBaptiste Daroussin for (icol = g->startcol; icol <= g->endcol; icol++)
298*c1c95addSBrooks Davis if (colwidth[icol] == min1)
2997295610fSBaptiste Daroussin necol++;
3007295610fSBaptiste Daroussin if (necol == 0)
3017295610fSBaptiste Daroussin continue;
3027295610fSBaptiste Daroussin width = min1 + (g->wanted - 1) / necol + 1;
3037295610fSBaptiste Daroussin if (width > min2)
3047295610fSBaptiste Daroussin width = min2;
3057295610fSBaptiste Daroussin if (wanted > width)
3067295610fSBaptiste Daroussin wanted = width;
3077295610fSBaptiste Daroussin }
3087295610fSBaptiste Daroussin
3096d38604fSBaptiste Daroussin /* Record the effect of the widening. */
3107295610fSBaptiste Daroussin
3117295610fSBaptiste Daroussin gp = &first_group;
3127295610fSBaptiste Daroussin while ((g = *gp) != NULL) {
3137295610fSBaptiste Daroussin done = 0;
3147295610fSBaptiste Daroussin for (icol = g->startcol; icol <= g->endcol; icol++) {
315*c1c95addSBrooks Davis if (colwidth[icol] != min1)
3167295610fSBaptiste Daroussin continue;
3177295610fSBaptiste Daroussin if (g->wanted <= wanted - min1) {
3186d38604fSBaptiste Daroussin tbl->cols[icol].width += g->wanted;
3197295610fSBaptiste Daroussin done = 1;
3207295610fSBaptiste Daroussin break;
3217295610fSBaptiste Daroussin }
3226d38604fSBaptiste Daroussin tbl->cols[icol].width = wanted;
3237295610fSBaptiste Daroussin g->wanted -= wanted - min1;
3247295610fSBaptiste Daroussin }
3257295610fSBaptiste Daroussin if (done) {
3267295610fSBaptiste Daroussin *gp = g->next;
3277295610fSBaptiste Daroussin free(g);
3287295610fSBaptiste Daroussin } else
329*c1c95addSBrooks Davis gp = &g->next;
3307295610fSBaptiste Daroussin }
3317295610fSBaptiste Daroussin }
332*c1c95addSBrooks Davis free(colwidth);
3337295610fSBaptiste Daroussin
3347295610fSBaptiste Daroussin /*
3357295610fSBaptiste Daroussin * Align numbers with text.
33661d06d6bSBaptiste Daroussin * Count columns to equalize and columns to maximize.
33761d06d6bSBaptiste Daroussin * Find maximum width of the columns to equalize.
33861d06d6bSBaptiste Daroussin * Find total width of the columns *not* to maximize.
33961d06d6bSBaptiste Daroussin */
34061d06d6bSBaptiste Daroussin
34161d06d6bSBaptiste Daroussin necol = nxcol = 0;
34261d06d6bSBaptiste Daroussin ewidth = xwidth = 0;
34361d06d6bSBaptiste Daroussin for (icol = 0; icol <= maxcol; icol++) {
34461d06d6bSBaptiste Daroussin col = tbl->cols + icol;
3457295610fSBaptiste Daroussin if (col->width > col->nwidth)
3467295610fSBaptiste Daroussin col->decimal += (col->width - col->nwidth) / 2;
34761d06d6bSBaptiste Daroussin if (col->flags & TBL_CELL_EQUAL) {
34861d06d6bSBaptiste Daroussin necol++;
34961d06d6bSBaptiste Daroussin if (ewidth < col->width)
35061d06d6bSBaptiste Daroussin ewidth = col->width;
35161d06d6bSBaptiste Daroussin }
35261d06d6bSBaptiste Daroussin if (col->flags & TBL_CELL_WMAX)
35361d06d6bSBaptiste Daroussin nxcol++;
35461d06d6bSBaptiste Daroussin else
35561d06d6bSBaptiste Daroussin xwidth += col->width;
35661d06d6bSBaptiste Daroussin }
35761d06d6bSBaptiste Daroussin
35861d06d6bSBaptiste Daroussin /*
35961d06d6bSBaptiste Daroussin * Equalize columns, if requested for any of them.
36061d06d6bSBaptiste Daroussin * Update total width of the columns not to maximize.
36161d06d6bSBaptiste Daroussin */
36261d06d6bSBaptiste Daroussin
36361d06d6bSBaptiste Daroussin if (necol) {
36461d06d6bSBaptiste Daroussin for (icol = 0; icol <= maxcol; icol++) {
36561d06d6bSBaptiste Daroussin col = tbl->cols + icol;
36661d06d6bSBaptiste Daroussin if ( ! (col->flags & TBL_CELL_EQUAL))
36761d06d6bSBaptiste Daroussin continue;
36861d06d6bSBaptiste Daroussin if (col->width == ewidth)
36961d06d6bSBaptiste Daroussin continue;
37061d06d6bSBaptiste Daroussin if (nxcol && rmargin)
37161d06d6bSBaptiste Daroussin xwidth += ewidth - col->width;
37261d06d6bSBaptiste Daroussin col->width = ewidth;
37361d06d6bSBaptiste Daroussin }
37461d06d6bSBaptiste Daroussin }
37561d06d6bSBaptiste Daroussin
37661d06d6bSBaptiste Daroussin /*
37761d06d6bSBaptiste Daroussin * If there are any columns to maximize, find the total
37861d06d6bSBaptiste Daroussin * available width, deducting 3n margins between columns.
37961d06d6bSBaptiste Daroussin * Distribute the available width evenly.
38061d06d6bSBaptiste Daroussin */
38161d06d6bSBaptiste Daroussin
38261d06d6bSBaptiste Daroussin if (nxcol && rmargin) {
38361d06d6bSBaptiste Daroussin xwidth += 3*maxcol +
38461d06d6bSBaptiste Daroussin (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ?
38561d06d6bSBaptiste Daroussin 2 : !!opts->lvert + !!opts->rvert);
38661d06d6bSBaptiste Daroussin if (rmargin <= offset + xwidth)
38761d06d6bSBaptiste Daroussin return;
38861d06d6bSBaptiste Daroussin xwidth = rmargin - offset - xwidth;
38961d06d6bSBaptiste Daroussin
39061d06d6bSBaptiste Daroussin /*
39161d06d6bSBaptiste Daroussin * Emulate a bug in GNU tbl width calculation that
39261d06d6bSBaptiste Daroussin * manifests itself for large numbers of x-columns.
39361d06d6bSBaptiste Daroussin * Emulating it for 5 x-columns gives identical
39461d06d6bSBaptiste Daroussin * behaviour for up to 6 x-columns.
39561d06d6bSBaptiste Daroussin */
39661d06d6bSBaptiste Daroussin
39761d06d6bSBaptiste Daroussin if (nxcol == 5) {
39861d06d6bSBaptiste Daroussin quirkcol = xwidth % nxcol + 2;
39961d06d6bSBaptiste Daroussin if (quirkcol != 3 && quirkcol != 4)
40061d06d6bSBaptiste Daroussin quirkcol = -1;
40161d06d6bSBaptiste Daroussin } else
40261d06d6bSBaptiste Daroussin quirkcol = -1;
40361d06d6bSBaptiste Daroussin
40461d06d6bSBaptiste Daroussin necol = 0;
40561d06d6bSBaptiste Daroussin ewidth = 0;
40661d06d6bSBaptiste Daroussin for (icol = 0; icol <= maxcol; icol++) {
40761d06d6bSBaptiste Daroussin col = tbl->cols + icol;
40861d06d6bSBaptiste Daroussin if ( ! (col->flags & TBL_CELL_WMAX))
40961d06d6bSBaptiste Daroussin continue;
41061d06d6bSBaptiste Daroussin col->width = (double)xwidth * ++necol / nxcol
41161d06d6bSBaptiste Daroussin - ewidth + 0.4995;
41261d06d6bSBaptiste Daroussin if (necol == quirkcol)
41361d06d6bSBaptiste Daroussin col->width--;
41461d06d6bSBaptiste Daroussin ewidth += col->width;
41561d06d6bSBaptiste Daroussin }
41661d06d6bSBaptiste Daroussin }
41761d06d6bSBaptiste Daroussin }
41861d06d6bSBaptiste Daroussin
4197295610fSBaptiste Daroussin static size_t
tblcalc_data(struct rofftbl * tbl,struct roffcol * col,const struct tbl_opts * opts,const struct tbl_dat * dp,size_t mw)42061d06d6bSBaptiste Daroussin tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
42161d06d6bSBaptiste Daroussin const struct tbl_opts *opts, const struct tbl_dat *dp, size_t mw)
42261d06d6bSBaptiste Daroussin {
42361d06d6bSBaptiste Daroussin size_t sz;
42461d06d6bSBaptiste Daroussin
42561d06d6bSBaptiste Daroussin /* Branch down into data sub-types. */
42661d06d6bSBaptiste Daroussin
42761d06d6bSBaptiste Daroussin switch (dp->layout->pos) {
42861d06d6bSBaptiste Daroussin case TBL_CELL_HORIZ:
42961d06d6bSBaptiste Daroussin case TBL_CELL_DHORIZ:
43061d06d6bSBaptiste Daroussin sz = (*tbl->len)(1, tbl->arg);
4317295610fSBaptiste Daroussin if (col != NULL && col->width < sz)
43261d06d6bSBaptiste Daroussin col->width = sz;
4337295610fSBaptiste Daroussin return sz;
43461d06d6bSBaptiste Daroussin case TBL_CELL_LONG:
43561d06d6bSBaptiste Daroussin case TBL_CELL_CENTRE:
43661d06d6bSBaptiste Daroussin case TBL_CELL_LEFT:
43761d06d6bSBaptiste Daroussin case TBL_CELL_RIGHT:
4387295610fSBaptiste Daroussin return tblcalc_literal(tbl, col, dp, mw);
43961d06d6bSBaptiste Daroussin case TBL_CELL_NUMBER:
4407295610fSBaptiste Daroussin return tblcalc_number(tbl, col, opts, dp);
44161d06d6bSBaptiste Daroussin case TBL_CELL_DOWN:
4427295610fSBaptiste Daroussin return 0;
44361d06d6bSBaptiste Daroussin default:
44461d06d6bSBaptiste Daroussin abort();
44561d06d6bSBaptiste Daroussin }
44661d06d6bSBaptiste Daroussin }
44761d06d6bSBaptiste Daroussin
4487295610fSBaptiste Daroussin static size_t
tblcalc_literal(struct rofftbl * tbl,struct roffcol * col,const struct tbl_dat * dp,size_t mw)44961d06d6bSBaptiste Daroussin tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
45061d06d6bSBaptiste Daroussin const struct tbl_dat *dp, size_t mw)
45161d06d6bSBaptiste Daroussin {
45261d06d6bSBaptiste Daroussin const char *str; /* Beginning of the first line. */
45361d06d6bSBaptiste Daroussin const char *beg; /* Beginning of the current line. */
45461d06d6bSBaptiste Daroussin char *end; /* End of the current line. */
45561d06d6bSBaptiste Daroussin size_t lsz; /* Length of the current line. */
45661d06d6bSBaptiste Daroussin size_t wsz; /* Length of the current word. */
4577295610fSBaptiste Daroussin size_t msz; /* Length of the longest line. */
45861d06d6bSBaptiste Daroussin
45961d06d6bSBaptiste Daroussin if (dp->string == NULL || *dp->string == '\0')
4607295610fSBaptiste Daroussin return 0;
46161d06d6bSBaptiste Daroussin str = mw ? mandoc_strdup(dp->string) : dp->string;
4627295610fSBaptiste Daroussin msz = lsz = 0;
46361d06d6bSBaptiste Daroussin for (beg = str; beg != NULL && *beg != '\0'; beg = end) {
46461d06d6bSBaptiste Daroussin end = mw ? strchr(beg, ' ') : NULL;
46561d06d6bSBaptiste Daroussin if (end != NULL) {
46661d06d6bSBaptiste Daroussin *end++ = '\0';
46761d06d6bSBaptiste Daroussin while (*end == ' ')
46861d06d6bSBaptiste Daroussin end++;
46961d06d6bSBaptiste Daroussin }
47061d06d6bSBaptiste Daroussin wsz = (*tbl->slen)(beg, tbl->arg);
47161d06d6bSBaptiste Daroussin if (mw && lsz && lsz + 1 + wsz <= mw)
47261d06d6bSBaptiste Daroussin lsz += 1 + wsz;
47361d06d6bSBaptiste Daroussin else
47461d06d6bSBaptiste Daroussin lsz = wsz;
4757295610fSBaptiste Daroussin if (msz < lsz)
4767295610fSBaptiste Daroussin msz = lsz;
47761d06d6bSBaptiste Daroussin }
47861d06d6bSBaptiste Daroussin if (mw)
47961d06d6bSBaptiste Daroussin free((void *)str);
4807295610fSBaptiste Daroussin if (col != NULL && col->width < msz)
4817295610fSBaptiste Daroussin col->width = msz;
4827295610fSBaptiste Daroussin return msz;
48361d06d6bSBaptiste Daroussin }
48461d06d6bSBaptiste Daroussin
4857295610fSBaptiste Daroussin static size_t
tblcalc_number(struct rofftbl * tbl,struct roffcol * col,const struct tbl_opts * opts,const struct tbl_dat * dp)48661d06d6bSBaptiste Daroussin tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
48761d06d6bSBaptiste Daroussin const struct tbl_opts *opts, const struct tbl_dat *dp)
48861d06d6bSBaptiste Daroussin {
4897295610fSBaptiste Daroussin const char *cp, *lastdigit, *lastpoint;
4907295610fSBaptiste Daroussin size_t intsz, totsz;
49161d06d6bSBaptiste Daroussin char buf[2];
49261d06d6bSBaptiste Daroussin
4937295610fSBaptiste Daroussin if (dp->string == NULL || *dp->string == '\0')
4947295610fSBaptiste Daroussin return 0;
4957295610fSBaptiste Daroussin
4967295610fSBaptiste Daroussin totsz = (*tbl->slen)(dp->string, tbl->arg);
4977295610fSBaptiste Daroussin if (col == NULL)
4987295610fSBaptiste Daroussin return totsz;
4997295610fSBaptiste Daroussin
50061d06d6bSBaptiste Daroussin /*
5017295610fSBaptiste Daroussin * Find the last digit and
5027295610fSBaptiste Daroussin * the last decimal point that is adjacent to a digit.
5037295610fSBaptiste Daroussin * The alignment indicator "\&" overrides everything.
50461d06d6bSBaptiste Daroussin */
50561d06d6bSBaptiste Daroussin
5067295610fSBaptiste Daroussin lastdigit = lastpoint = NULL;
5077295610fSBaptiste Daroussin for (cp = dp->string; cp[0] != '\0'; cp++) {
5087295610fSBaptiste Daroussin if (cp[0] == '\\' && cp[1] == '&') {
5097295610fSBaptiste Daroussin lastdigit = lastpoint = cp;
5107295610fSBaptiste Daroussin break;
5117295610fSBaptiste Daroussin } else if (cp[0] == opts->decimal &&
5127295610fSBaptiste Daroussin (isdigit((unsigned char)cp[1]) ||
5137295610fSBaptiste Daroussin (cp > dp->string && isdigit((unsigned char)cp[-1]))))
5147295610fSBaptiste Daroussin lastpoint = cp;
5157295610fSBaptiste Daroussin else if (isdigit((unsigned char)cp[0]))
5167295610fSBaptiste Daroussin lastdigit = cp;
51761d06d6bSBaptiste Daroussin }
5187295610fSBaptiste Daroussin
5197295610fSBaptiste Daroussin /* Not a number, treat as a literal string. */
5207295610fSBaptiste Daroussin
5217295610fSBaptiste Daroussin if (lastdigit == NULL) {
5227295610fSBaptiste Daroussin if (col != NULL && col->width < totsz)
5237295610fSBaptiste Daroussin col->width = totsz;
5247295610fSBaptiste Daroussin return totsz;
5257295610fSBaptiste Daroussin }
5267295610fSBaptiste Daroussin
5277295610fSBaptiste Daroussin /* Measure the width of the integer part. */
5287295610fSBaptiste Daroussin
5297295610fSBaptiste Daroussin if (lastpoint == NULL)
5307295610fSBaptiste Daroussin lastpoint = lastdigit + 1;
5317295610fSBaptiste Daroussin intsz = 0;
5327295610fSBaptiste Daroussin buf[1] = '\0';
5337295610fSBaptiste Daroussin for (cp = dp->string; cp < lastpoint; cp++) {
5347295610fSBaptiste Daroussin buf[0] = cp[0];
5357295610fSBaptiste Daroussin intsz += (*tbl->slen)(buf, tbl->arg);
5367295610fSBaptiste Daroussin }
5377295610fSBaptiste Daroussin
5387295610fSBaptiste Daroussin /*
5397295610fSBaptiste Daroussin * If this number has more integer digits than all numbers
5407295610fSBaptiste Daroussin * seen on earlier lines, shift them all to the right.
5417295610fSBaptiste Daroussin * If it has fewer, shift this number to the right.
5427295610fSBaptiste Daroussin */
5437295610fSBaptiste Daroussin
5447295610fSBaptiste Daroussin if (intsz > col->decimal) {
5457295610fSBaptiste Daroussin col->nwidth += intsz - col->decimal;
5467295610fSBaptiste Daroussin col->decimal = intsz;
54761d06d6bSBaptiste Daroussin } else
5487295610fSBaptiste Daroussin totsz += col->decimal - intsz;
54961d06d6bSBaptiste Daroussin
5507295610fSBaptiste Daroussin /* Update the maximum total width seen so far. */
55161d06d6bSBaptiste Daroussin
5527295610fSBaptiste Daroussin if (totsz > col->nwidth)
5537295610fSBaptiste Daroussin col->nwidth = totsz;
5546d38604fSBaptiste Daroussin if (col->nwidth > col->width)
5556d38604fSBaptiste Daroussin col->width = col->nwidth;
5567295610fSBaptiste Daroussin return totsz;
55761d06d6bSBaptiste Daroussin }
558