xref: /freebsd/contrib/mandoc/out.c (revision 6d38604f)
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