1*6d38604fSBaptiste Daroussin /* $Id: out.c,v 1.82 2021/09/07 17:07:58 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
361d06d6bSBaptiste Daroussin * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4*6d38604fSBaptiste Daroussin * Copyright (c) 2011, 2014, 2015, 2017, 2018, 2019, 2021
5*6d38604fSBaptiste 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>
26*6d38604fSBaptiste 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"
32*6d38604fSBaptiste 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;
1267295610fSBaptiste Daroussin size_t ewidth, min1, min2, wanted, width, xwidth;
1277295610fSBaptiste Daroussin int done, icol, maxcol, necol, nxcol, quirkcol;
12861d06d6bSBaptiste Daroussin
12961d06d6bSBaptiste Daroussin /*
13061d06d6bSBaptiste Daroussin * Allocate the master column specifiers. These will hold the
13161d06d6bSBaptiste Daroussin * widths and decimal positions for all cells in the column. It
13261d06d6bSBaptiste Daroussin * must be freed and nullified by the caller.
13361d06d6bSBaptiste Daroussin */
13461d06d6bSBaptiste Daroussin
1357295610fSBaptiste Daroussin assert(tbl->cols == NULL);
1367295610fSBaptiste Daroussin tbl->cols = mandoc_calloc((size_t)sp_first->opts->cols,
13761d06d6bSBaptiste Daroussin sizeof(struct roffcol));
1387295610fSBaptiste Daroussin opts = sp_first->opts;
13961d06d6bSBaptiste Daroussin
1407295610fSBaptiste Daroussin maxcol = -1;
1417295610fSBaptiste Daroussin first_group = NULL;
1427295610fSBaptiste Daroussin for (sp = sp_first; sp != NULL; sp = sp->next) {
1437295610fSBaptiste Daroussin if (sp->pos != TBL_SPAN_DATA)
14461d06d6bSBaptiste Daroussin continue;
1457295610fSBaptiste Daroussin
14661d06d6bSBaptiste Daroussin /*
14761d06d6bSBaptiste Daroussin * Account for the data cells in the layout, matching it
14861d06d6bSBaptiste Daroussin * to data cells in the data section.
14961d06d6bSBaptiste Daroussin */
1507295610fSBaptiste Daroussin
1517295610fSBaptiste Daroussin gp = &first_group;
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 /*
1927295610fSBaptiste Daroussin * Build an ordered, 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
1977295610fSBaptiste Daroussin while (*gp != NULL && ((*gp)->startcol < icol ||
1987295610fSBaptiste Daroussin (*gp)->endcol < icol + dp->hspans))
1997295610fSBaptiste Daroussin gp = &(*gp)->next;
2007295610fSBaptiste Daroussin if (*gp == NULL || (*gp)->startcol > icol ||
2017295610fSBaptiste Daroussin (*gp)->endcol > icol + dp->hspans) {
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 /*
214*6d38604fSBaptiste Daroussin * The minimum width of columns explicitly specified
215*6d38604fSBaptiste Daroussin * in the layout is 1n.
2167295610fSBaptiste Daroussin */
2177295610fSBaptiste Daroussin
218*6d38604fSBaptiste Daroussin if (maxcol < sp_first->opts->cols - 1)
219*6d38604fSBaptiste Daroussin maxcol = sp_first->opts->cols - 1;
220*6d38604fSBaptiste Daroussin for (icol = 0; icol <= maxcol; icol++) {
221*6d38604fSBaptiste Daroussin col = tbl->cols + icol;
222*6d38604fSBaptiste Daroussin if (col->width < 1)
223*6d38604fSBaptiste Daroussin col->width = 1;
224*6d38604fSBaptiste Daroussin
225*6d38604fSBaptiste Daroussin /*
226*6d38604fSBaptiste Daroussin * Column spacings are needed for span width
227*6d38604fSBaptiste Daroussin * calculations, so set the default values now.
228*6d38604fSBaptiste Daroussin */
229*6d38604fSBaptiste Daroussin
230*6d38604fSBaptiste Daroussin if (col->spacing == SIZE_MAX || icol == maxcol)
231*6d38604fSBaptiste Daroussin col->spacing = 3;
232*6d38604fSBaptiste 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
2507295610fSBaptiste Daroussin (*gp)->wanted -= width;
2517295610fSBaptiste Daroussin }
2527295610fSBaptiste Daroussin if (done) {
2537295610fSBaptiste Daroussin *gp = g->next;
2547295610fSBaptiste Daroussin free(g);
2557295610fSBaptiste Daroussin } else
2567295610fSBaptiste Daroussin gp = &(*gp)->next;
2577295610fSBaptiste Daroussin }
2587295610fSBaptiste Daroussin
2597295610fSBaptiste Daroussin while (first_group != NULL) {
2607295610fSBaptiste Daroussin
2617295610fSBaptiste Daroussin /*
2627295610fSBaptiste Daroussin * Find the smallest and second smallest column width
2637295610fSBaptiste Daroussin * among the columns which may need expamsion.
2647295610fSBaptiste Daroussin */
2657295610fSBaptiste Daroussin
2667295610fSBaptiste Daroussin min1 = min2 = SIZE_MAX;
2677295610fSBaptiste Daroussin for (icol = 0; icol <= maxcol; icol++) {
268*6d38604fSBaptiste Daroussin width = tbl->cols[icol].width;
269*6d38604fSBaptiste Daroussin if (min1 > width) {
2707295610fSBaptiste Daroussin min2 = min1;
271*6d38604fSBaptiste Daroussin min1 = width;
272*6d38604fSBaptiste Daroussin } else if (min1 < width && min2 > width)
273*6d38604fSBaptiste Daroussin min2 = width;
2747295610fSBaptiste Daroussin }
2757295610fSBaptiste Daroussin
2767295610fSBaptiste Daroussin /*
2777295610fSBaptiste Daroussin * Find the minimum wanted width
2787295610fSBaptiste Daroussin * for any one of the narrowest columns,
2797295610fSBaptiste Daroussin * and mark the columns wanting that width.
2807295610fSBaptiste Daroussin */
2817295610fSBaptiste Daroussin
2827295610fSBaptiste Daroussin wanted = min2;
2837295610fSBaptiste Daroussin for (g = first_group; g != NULL; g = g->next) {
2847295610fSBaptiste Daroussin necol = 0;
2857295610fSBaptiste Daroussin for (icol = g->startcol; icol <= g->endcol; icol++)
2867295610fSBaptiste Daroussin if (tbl->cols[icol].width == min1)
2877295610fSBaptiste Daroussin necol++;
2887295610fSBaptiste Daroussin if (necol == 0)
2897295610fSBaptiste Daroussin continue;
2907295610fSBaptiste Daroussin width = min1 + (g->wanted - 1) / necol + 1;
2917295610fSBaptiste Daroussin if (width > min2)
2927295610fSBaptiste Daroussin width = min2;
2937295610fSBaptiste Daroussin if (wanted > width)
2947295610fSBaptiste Daroussin wanted = width;
2957295610fSBaptiste Daroussin }
2967295610fSBaptiste Daroussin
297*6d38604fSBaptiste Daroussin /* Record the effect of the widening. */
2987295610fSBaptiste Daroussin
2997295610fSBaptiste Daroussin gp = &first_group;
3007295610fSBaptiste Daroussin while ((g = *gp) != NULL) {
3017295610fSBaptiste Daroussin done = 0;
3027295610fSBaptiste Daroussin for (icol = g->startcol; icol <= g->endcol; icol++) {
303*6d38604fSBaptiste Daroussin if (tbl->cols[icol].width != min1)
3047295610fSBaptiste Daroussin continue;
3057295610fSBaptiste Daroussin if (g->wanted <= wanted - min1) {
306*6d38604fSBaptiste Daroussin tbl->cols[icol].width += g->wanted;
3077295610fSBaptiste Daroussin done = 1;
3087295610fSBaptiste Daroussin break;
3097295610fSBaptiste Daroussin }
310*6d38604fSBaptiste Daroussin tbl->cols[icol].width = wanted;
3117295610fSBaptiste Daroussin g->wanted -= wanted - min1;
3127295610fSBaptiste Daroussin }
3137295610fSBaptiste Daroussin if (done) {
3147295610fSBaptiste Daroussin *gp = g->next;
3157295610fSBaptiste Daroussin free(g);
3167295610fSBaptiste Daroussin } else
3177295610fSBaptiste Daroussin gp = &(*gp)->next;
3187295610fSBaptiste Daroussin }
3197295610fSBaptiste Daroussin }
3207295610fSBaptiste Daroussin
3217295610fSBaptiste Daroussin /*
3227295610fSBaptiste Daroussin * Align numbers with text.
32361d06d6bSBaptiste Daroussin * Count columns to equalize and columns to maximize.
32461d06d6bSBaptiste Daroussin * Find maximum width of the columns to equalize.
32561d06d6bSBaptiste Daroussin * Find total width of the columns *not* to maximize.
32661d06d6bSBaptiste Daroussin */
32761d06d6bSBaptiste Daroussin
32861d06d6bSBaptiste Daroussin necol = nxcol = 0;
32961d06d6bSBaptiste Daroussin ewidth = xwidth = 0;
33061d06d6bSBaptiste Daroussin for (icol = 0; icol <= maxcol; icol++) {
33161d06d6bSBaptiste Daroussin col = tbl->cols + icol;
3327295610fSBaptiste Daroussin if (col->width > col->nwidth)
3337295610fSBaptiste Daroussin col->decimal += (col->width - col->nwidth) / 2;
33461d06d6bSBaptiste Daroussin if (col->flags & TBL_CELL_EQUAL) {
33561d06d6bSBaptiste Daroussin necol++;
33661d06d6bSBaptiste Daroussin if (ewidth < col->width)
33761d06d6bSBaptiste Daroussin ewidth = col->width;
33861d06d6bSBaptiste Daroussin }
33961d06d6bSBaptiste Daroussin if (col->flags & TBL_CELL_WMAX)
34061d06d6bSBaptiste Daroussin nxcol++;
34161d06d6bSBaptiste Daroussin else
34261d06d6bSBaptiste Daroussin xwidth += col->width;
34361d06d6bSBaptiste Daroussin }
34461d06d6bSBaptiste Daroussin
34561d06d6bSBaptiste Daroussin /*
34661d06d6bSBaptiste Daroussin * Equalize columns, if requested for any of them.
34761d06d6bSBaptiste Daroussin * Update total width of the columns not to maximize.
34861d06d6bSBaptiste Daroussin */
34961d06d6bSBaptiste Daroussin
35061d06d6bSBaptiste Daroussin if (necol) {
35161d06d6bSBaptiste Daroussin for (icol = 0; icol <= maxcol; icol++) {
35261d06d6bSBaptiste Daroussin col = tbl->cols + icol;
35361d06d6bSBaptiste Daroussin if ( ! (col->flags & TBL_CELL_EQUAL))
35461d06d6bSBaptiste Daroussin continue;
35561d06d6bSBaptiste Daroussin if (col->width == ewidth)
35661d06d6bSBaptiste Daroussin continue;
35761d06d6bSBaptiste Daroussin if (nxcol && rmargin)
35861d06d6bSBaptiste Daroussin xwidth += ewidth - col->width;
35961d06d6bSBaptiste Daroussin col->width = ewidth;
36061d06d6bSBaptiste Daroussin }
36161d06d6bSBaptiste Daroussin }
36261d06d6bSBaptiste Daroussin
36361d06d6bSBaptiste Daroussin /*
36461d06d6bSBaptiste Daroussin * If there are any columns to maximize, find the total
36561d06d6bSBaptiste Daroussin * available width, deducting 3n margins between columns.
36661d06d6bSBaptiste Daroussin * Distribute the available width evenly.
36761d06d6bSBaptiste Daroussin */
36861d06d6bSBaptiste Daroussin
36961d06d6bSBaptiste Daroussin if (nxcol && rmargin) {
37061d06d6bSBaptiste Daroussin xwidth += 3*maxcol +
37161d06d6bSBaptiste Daroussin (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ?
37261d06d6bSBaptiste Daroussin 2 : !!opts->lvert + !!opts->rvert);
37361d06d6bSBaptiste Daroussin if (rmargin <= offset + xwidth)
37461d06d6bSBaptiste Daroussin return;
37561d06d6bSBaptiste Daroussin xwidth = rmargin - offset - xwidth;
37661d06d6bSBaptiste Daroussin
37761d06d6bSBaptiste Daroussin /*
37861d06d6bSBaptiste Daroussin * Emulate a bug in GNU tbl width calculation that
37961d06d6bSBaptiste Daroussin * manifests itself for large numbers of x-columns.
38061d06d6bSBaptiste Daroussin * Emulating it for 5 x-columns gives identical
38161d06d6bSBaptiste Daroussin * behaviour for up to 6 x-columns.
38261d06d6bSBaptiste Daroussin */
38361d06d6bSBaptiste Daroussin
38461d06d6bSBaptiste Daroussin if (nxcol == 5) {
38561d06d6bSBaptiste Daroussin quirkcol = xwidth % nxcol + 2;
38661d06d6bSBaptiste Daroussin if (quirkcol != 3 && quirkcol != 4)
38761d06d6bSBaptiste Daroussin quirkcol = -1;
38861d06d6bSBaptiste Daroussin } else
38961d06d6bSBaptiste Daroussin quirkcol = -1;
39061d06d6bSBaptiste Daroussin
39161d06d6bSBaptiste Daroussin necol = 0;
39261d06d6bSBaptiste Daroussin ewidth = 0;
39361d06d6bSBaptiste Daroussin for (icol = 0; icol <= maxcol; icol++) {
39461d06d6bSBaptiste Daroussin col = tbl->cols + icol;
39561d06d6bSBaptiste Daroussin if ( ! (col->flags & TBL_CELL_WMAX))
39661d06d6bSBaptiste Daroussin continue;
39761d06d6bSBaptiste Daroussin col->width = (double)xwidth * ++necol / nxcol
39861d06d6bSBaptiste Daroussin - ewidth + 0.4995;
39961d06d6bSBaptiste Daroussin if (necol == quirkcol)
40061d06d6bSBaptiste Daroussin col->width--;
40161d06d6bSBaptiste Daroussin ewidth += col->width;
40261d06d6bSBaptiste Daroussin }
40361d06d6bSBaptiste Daroussin }
40461d06d6bSBaptiste Daroussin }
40561d06d6bSBaptiste Daroussin
4067295610fSBaptiste 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)40761d06d6bSBaptiste Daroussin tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
40861d06d6bSBaptiste Daroussin const struct tbl_opts *opts, const struct tbl_dat *dp, size_t mw)
40961d06d6bSBaptiste Daroussin {
41061d06d6bSBaptiste Daroussin size_t sz;
41161d06d6bSBaptiste Daroussin
41261d06d6bSBaptiste Daroussin /* Branch down into data sub-types. */
41361d06d6bSBaptiste Daroussin
41461d06d6bSBaptiste Daroussin switch (dp->layout->pos) {
41561d06d6bSBaptiste Daroussin case TBL_CELL_HORIZ:
41661d06d6bSBaptiste Daroussin case TBL_CELL_DHORIZ:
41761d06d6bSBaptiste Daroussin sz = (*tbl->len)(1, tbl->arg);
4187295610fSBaptiste Daroussin if (col != NULL && col->width < sz)
41961d06d6bSBaptiste Daroussin col->width = sz;
4207295610fSBaptiste Daroussin return sz;
42161d06d6bSBaptiste Daroussin case TBL_CELL_LONG:
42261d06d6bSBaptiste Daroussin case TBL_CELL_CENTRE:
42361d06d6bSBaptiste Daroussin case TBL_CELL_LEFT:
42461d06d6bSBaptiste Daroussin case TBL_CELL_RIGHT:
4257295610fSBaptiste Daroussin return tblcalc_literal(tbl, col, dp, mw);
42661d06d6bSBaptiste Daroussin case TBL_CELL_NUMBER:
4277295610fSBaptiste Daroussin return tblcalc_number(tbl, col, opts, dp);
42861d06d6bSBaptiste Daroussin case TBL_CELL_DOWN:
4297295610fSBaptiste Daroussin return 0;
43061d06d6bSBaptiste Daroussin default:
43161d06d6bSBaptiste Daroussin abort();
43261d06d6bSBaptiste Daroussin }
43361d06d6bSBaptiste Daroussin }
43461d06d6bSBaptiste Daroussin
4357295610fSBaptiste Daroussin static size_t
tblcalc_literal(struct rofftbl * tbl,struct roffcol * col,const struct tbl_dat * dp,size_t mw)43661d06d6bSBaptiste Daroussin tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
43761d06d6bSBaptiste Daroussin const struct tbl_dat *dp, size_t mw)
43861d06d6bSBaptiste Daroussin {
43961d06d6bSBaptiste Daroussin const char *str; /* Beginning of the first line. */
44061d06d6bSBaptiste Daroussin const char *beg; /* Beginning of the current line. */
44161d06d6bSBaptiste Daroussin char *end; /* End of the current line. */
44261d06d6bSBaptiste Daroussin size_t lsz; /* Length of the current line. */
44361d06d6bSBaptiste Daroussin size_t wsz; /* Length of the current word. */
4447295610fSBaptiste Daroussin size_t msz; /* Length of the longest line. */
44561d06d6bSBaptiste Daroussin
44661d06d6bSBaptiste Daroussin if (dp->string == NULL || *dp->string == '\0')
4477295610fSBaptiste Daroussin return 0;
44861d06d6bSBaptiste Daroussin str = mw ? mandoc_strdup(dp->string) : dp->string;
4497295610fSBaptiste Daroussin msz = lsz = 0;
45061d06d6bSBaptiste Daroussin for (beg = str; beg != NULL && *beg != '\0'; beg = end) {
45161d06d6bSBaptiste Daroussin end = mw ? strchr(beg, ' ') : NULL;
45261d06d6bSBaptiste Daroussin if (end != NULL) {
45361d06d6bSBaptiste Daroussin *end++ = '\0';
45461d06d6bSBaptiste Daroussin while (*end == ' ')
45561d06d6bSBaptiste Daroussin end++;
45661d06d6bSBaptiste Daroussin }
45761d06d6bSBaptiste Daroussin wsz = (*tbl->slen)(beg, tbl->arg);
45861d06d6bSBaptiste Daroussin if (mw && lsz && lsz + 1 + wsz <= mw)
45961d06d6bSBaptiste Daroussin lsz += 1 + wsz;
46061d06d6bSBaptiste Daroussin else
46161d06d6bSBaptiste Daroussin lsz = wsz;
4627295610fSBaptiste Daroussin if (msz < lsz)
4637295610fSBaptiste Daroussin msz = lsz;
46461d06d6bSBaptiste Daroussin }
46561d06d6bSBaptiste Daroussin if (mw)
46661d06d6bSBaptiste Daroussin free((void *)str);
4677295610fSBaptiste Daroussin if (col != NULL && col->width < msz)
4687295610fSBaptiste Daroussin col->width = msz;
4697295610fSBaptiste Daroussin return msz;
47061d06d6bSBaptiste Daroussin }
47161d06d6bSBaptiste Daroussin
4727295610fSBaptiste Daroussin static size_t
tblcalc_number(struct rofftbl * tbl,struct roffcol * col,const struct tbl_opts * opts,const struct tbl_dat * dp)47361d06d6bSBaptiste Daroussin tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
47461d06d6bSBaptiste Daroussin const struct tbl_opts *opts, const struct tbl_dat *dp)
47561d06d6bSBaptiste Daroussin {
4767295610fSBaptiste Daroussin const char *cp, *lastdigit, *lastpoint;
4777295610fSBaptiste Daroussin size_t intsz, totsz;
47861d06d6bSBaptiste Daroussin char buf[2];
47961d06d6bSBaptiste Daroussin
4807295610fSBaptiste Daroussin if (dp->string == NULL || *dp->string == '\0')
4817295610fSBaptiste Daroussin return 0;
4827295610fSBaptiste Daroussin
4837295610fSBaptiste Daroussin totsz = (*tbl->slen)(dp->string, tbl->arg);
4847295610fSBaptiste Daroussin if (col == NULL)
4857295610fSBaptiste Daroussin return totsz;
4867295610fSBaptiste Daroussin
48761d06d6bSBaptiste Daroussin /*
4887295610fSBaptiste Daroussin * Find the last digit and
4897295610fSBaptiste Daroussin * the last decimal point that is adjacent to a digit.
4907295610fSBaptiste Daroussin * The alignment indicator "\&" overrides everything.
49161d06d6bSBaptiste Daroussin */
49261d06d6bSBaptiste Daroussin
4937295610fSBaptiste Daroussin lastdigit = lastpoint = NULL;
4947295610fSBaptiste Daroussin for (cp = dp->string; cp[0] != '\0'; cp++) {
4957295610fSBaptiste Daroussin if (cp[0] == '\\' && cp[1] == '&') {
4967295610fSBaptiste Daroussin lastdigit = lastpoint = cp;
4977295610fSBaptiste Daroussin break;
4987295610fSBaptiste Daroussin } else if (cp[0] == opts->decimal &&
4997295610fSBaptiste Daroussin (isdigit((unsigned char)cp[1]) ||
5007295610fSBaptiste Daroussin (cp > dp->string && isdigit((unsigned char)cp[-1]))))
5017295610fSBaptiste Daroussin lastpoint = cp;
5027295610fSBaptiste Daroussin else if (isdigit((unsigned char)cp[0]))
5037295610fSBaptiste Daroussin lastdigit = cp;
50461d06d6bSBaptiste Daroussin }
5057295610fSBaptiste Daroussin
5067295610fSBaptiste Daroussin /* Not a number, treat as a literal string. */
5077295610fSBaptiste Daroussin
5087295610fSBaptiste Daroussin if (lastdigit == NULL) {
5097295610fSBaptiste Daroussin if (col != NULL && col->width < totsz)
5107295610fSBaptiste Daroussin col->width = totsz;
5117295610fSBaptiste Daroussin return totsz;
5127295610fSBaptiste Daroussin }
5137295610fSBaptiste Daroussin
5147295610fSBaptiste Daroussin /* Measure the width of the integer part. */
5157295610fSBaptiste Daroussin
5167295610fSBaptiste Daroussin if (lastpoint == NULL)
5177295610fSBaptiste Daroussin lastpoint = lastdigit + 1;
5187295610fSBaptiste Daroussin intsz = 0;
5197295610fSBaptiste Daroussin buf[1] = '\0';
5207295610fSBaptiste Daroussin for (cp = dp->string; cp < lastpoint; cp++) {
5217295610fSBaptiste Daroussin buf[0] = cp[0];
5227295610fSBaptiste Daroussin intsz += (*tbl->slen)(buf, tbl->arg);
5237295610fSBaptiste Daroussin }
5247295610fSBaptiste Daroussin
5257295610fSBaptiste Daroussin /*
5267295610fSBaptiste Daroussin * If this number has more integer digits than all numbers
5277295610fSBaptiste Daroussin * seen on earlier lines, shift them all to the right.
5287295610fSBaptiste Daroussin * If it has fewer, shift this number to the right.
5297295610fSBaptiste Daroussin */
5307295610fSBaptiste Daroussin
5317295610fSBaptiste Daroussin if (intsz > col->decimal) {
5327295610fSBaptiste Daroussin col->nwidth += intsz - col->decimal;
5337295610fSBaptiste Daroussin col->decimal = intsz;
53461d06d6bSBaptiste Daroussin } else
5357295610fSBaptiste Daroussin totsz += col->decimal - intsz;
53661d06d6bSBaptiste Daroussin
5377295610fSBaptiste Daroussin /* Update the maximum total width seen so far. */
53861d06d6bSBaptiste Daroussin
5397295610fSBaptiste Daroussin if (totsz > col->nwidth)
5407295610fSBaptiste Daroussin col->nwidth = totsz;
541*6d38604fSBaptiste Daroussin if (col->nwidth > col->width)
542*6d38604fSBaptiste Daroussin col->width = col->nwidth;
5437295610fSBaptiste Daroussin return totsz;
54461d06d6bSBaptiste Daroussin }
545