xref: /freebsd/contrib/mandoc/out.c (revision 45a5aec3f156d8a01fe1ea4ec87c1b1d489f13ac)
1*45a5aec3SBaptiste Daroussin /*	$Id: out.c,v 1.78 2019/03/29 21:27:06 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
361d06d6bSBaptiste Daroussin  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
47295610fSBaptiste Daroussin  * Copyright (c) 2011,2014,2015,2017,2018 Ingo Schwarze <schwarze@openbsd.org>
561d06d6bSBaptiste Daroussin  *
661d06d6bSBaptiste Daroussin  * Permission to use, copy, modify, and distribute this software for any
761d06d6bSBaptiste Daroussin  * purpose with or without fee is hereby granted, provided that the above
861d06d6bSBaptiste Daroussin  * copyright notice and this permission notice appear in all copies.
961d06d6bSBaptiste Daroussin  *
1061d06d6bSBaptiste Daroussin  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1161d06d6bSBaptiste Daroussin  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1261d06d6bSBaptiste Daroussin  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1361d06d6bSBaptiste Daroussin  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1461d06d6bSBaptiste Daroussin  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1561d06d6bSBaptiste Daroussin  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1661d06d6bSBaptiste Daroussin  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1761d06d6bSBaptiste Daroussin  */
1861d06d6bSBaptiste Daroussin #include "config.h"
1961d06d6bSBaptiste Daroussin 
2061d06d6bSBaptiste Daroussin #include <sys/types.h>
2161d06d6bSBaptiste Daroussin 
2261d06d6bSBaptiste Daroussin #include <assert.h>
237295610fSBaptiste Daroussin #include <ctype.h>
2461d06d6bSBaptiste Daroussin #include <stdint.h>
2561d06d6bSBaptiste Daroussin #include <stdlib.h>
2661d06d6bSBaptiste Daroussin #include <string.h>
2761d06d6bSBaptiste Daroussin #include <time.h>
2861d06d6bSBaptiste Daroussin 
2961d06d6bSBaptiste Daroussin #include "mandoc_aux.h"
307295610fSBaptiste Daroussin #include "tbl.h"
3161d06d6bSBaptiste Daroussin #include "out.h"
3261d06d6bSBaptiste Daroussin 
337295610fSBaptiste Daroussin struct	tbl_colgroup {
347295610fSBaptiste Daroussin 	struct tbl_colgroup	*next;
357295610fSBaptiste Daroussin 	size_t			 wanted;
367295610fSBaptiste Daroussin 	int			 startcol;
377295610fSBaptiste Daroussin 	int			 endcol;
387295610fSBaptiste Daroussin };
397295610fSBaptiste Daroussin 
407295610fSBaptiste Daroussin static	size_t	tblcalc_data(struct rofftbl *, struct roffcol *,
4161d06d6bSBaptiste Daroussin 			const struct tbl_opts *, const struct tbl_dat *,
4261d06d6bSBaptiste Daroussin 			size_t);
437295610fSBaptiste Daroussin static	size_t	tblcalc_literal(struct rofftbl *, struct roffcol *,
4461d06d6bSBaptiste Daroussin 			const struct tbl_dat *, size_t);
457295610fSBaptiste Daroussin static	size_t	tblcalc_number(struct rofftbl *, struct roffcol *,
4661d06d6bSBaptiste Daroussin 			const struct tbl_opts *, const struct tbl_dat *);
4761d06d6bSBaptiste Daroussin 
4861d06d6bSBaptiste Daroussin 
4961d06d6bSBaptiste Daroussin /*
5061d06d6bSBaptiste Daroussin  * Parse the *src string and store a scaling unit into *dst.
5161d06d6bSBaptiste Daroussin  * If the string doesn't specify the unit, use the default.
5261d06d6bSBaptiste Daroussin  * If no default is specified, fail.
5361d06d6bSBaptiste Daroussin  * Return a pointer to the byte after the last byte used,
5461d06d6bSBaptiste Daroussin  * or NULL on total failure.
5561d06d6bSBaptiste Daroussin  */
5661d06d6bSBaptiste Daroussin const char *
5761d06d6bSBaptiste Daroussin a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
5861d06d6bSBaptiste Daroussin {
5961d06d6bSBaptiste Daroussin 	char		*endptr;
6061d06d6bSBaptiste Daroussin 
6161d06d6bSBaptiste Daroussin 	dst->unit = def == SCALE_MAX ? SCALE_BU : def;
6261d06d6bSBaptiste Daroussin 	dst->scale = strtod(src, &endptr);
6361d06d6bSBaptiste Daroussin 	if (endptr == src)
6461d06d6bSBaptiste Daroussin 		return NULL;
6561d06d6bSBaptiste Daroussin 
6661d06d6bSBaptiste Daroussin 	switch (*endptr++) {
6761d06d6bSBaptiste Daroussin 	case 'c':
6861d06d6bSBaptiste Daroussin 		dst->unit = SCALE_CM;
6961d06d6bSBaptiste Daroussin 		break;
7061d06d6bSBaptiste Daroussin 	case 'i':
7161d06d6bSBaptiste Daroussin 		dst->unit = SCALE_IN;
7261d06d6bSBaptiste Daroussin 		break;
7361d06d6bSBaptiste Daroussin 	case 'f':
7461d06d6bSBaptiste Daroussin 		dst->unit = SCALE_FS;
7561d06d6bSBaptiste Daroussin 		break;
7661d06d6bSBaptiste Daroussin 	case 'M':
7761d06d6bSBaptiste Daroussin 		dst->unit = SCALE_MM;
7861d06d6bSBaptiste Daroussin 		break;
7961d06d6bSBaptiste Daroussin 	case 'm':
8061d06d6bSBaptiste Daroussin 		dst->unit = SCALE_EM;
8161d06d6bSBaptiste Daroussin 		break;
8261d06d6bSBaptiste Daroussin 	case 'n':
8361d06d6bSBaptiste Daroussin 		dst->unit = SCALE_EN;
8461d06d6bSBaptiste Daroussin 		break;
8561d06d6bSBaptiste Daroussin 	case 'P':
8661d06d6bSBaptiste Daroussin 		dst->unit = SCALE_PC;
8761d06d6bSBaptiste Daroussin 		break;
8861d06d6bSBaptiste Daroussin 	case 'p':
8961d06d6bSBaptiste Daroussin 		dst->unit = SCALE_PT;
9061d06d6bSBaptiste Daroussin 		break;
9161d06d6bSBaptiste Daroussin 	case 'u':
9261d06d6bSBaptiste Daroussin 		dst->unit = SCALE_BU;
9361d06d6bSBaptiste Daroussin 		break;
9461d06d6bSBaptiste Daroussin 	case 'v':
9561d06d6bSBaptiste Daroussin 		dst->unit = SCALE_VS;
9661d06d6bSBaptiste Daroussin 		break;
9761d06d6bSBaptiste Daroussin 	default:
9861d06d6bSBaptiste Daroussin 		endptr--;
9961d06d6bSBaptiste Daroussin 		if (SCALE_MAX == def)
10061d06d6bSBaptiste Daroussin 			return NULL;
10161d06d6bSBaptiste Daroussin 		dst->unit = def;
10261d06d6bSBaptiste Daroussin 		break;
10361d06d6bSBaptiste Daroussin 	}
10461d06d6bSBaptiste Daroussin 	return endptr;
10561d06d6bSBaptiste Daroussin }
10661d06d6bSBaptiste Daroussin 
10761d06d6bSBaptiste Daroussin /*
10861d06d6bSBaptiste Daroussin  * Calculate the abstract widths and decimal positions of columns in a
10961d06d6bSBaptiste Daroussin  * table.  This routine allocates the columns structures then runs over
11061d06d6bSBaptiste Daroussin  * all rows and cells in the table.  The function pointers in "tbl" are
11161d06d6bSBaptiste Daroussin  * used for the actual width calculations.
11261d06d6bSBaptiste Daroussin  */
11361d06d6bSBaptiste Daroussin void
1147295610fSBaptiste Daroussin tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first,
11561d06d6bSBaptiste Daroussin     size_t offset, size_t rmargin)
11661d06d6bSBaptiste Daroussin {
11761d06d6bSBaptiste Daroussin 	struct roffsu		 su;
11861d06d6bSBaptiste Daroussin 	const struct tbl_opts	*opts;
1197295610fSBaptiste Daroussin 	const struct tbl_span	*sp;
12061d06d6bSBaptiste Daroussin 	const struct tbl_dat	*dp;
12161d06d6bSBaptiste Daroussin 	struct roffcol		*col;
1227295610fSBaptiste Daroussin 	struct tbl_colgroup	*first_group, **gp, *g;
1237295610fSBaptiste Daroussin 	size_t			*colwidth;
1247295610fSBaptiste Daroussin 	size_t			 ewidth, min1, min2, wanted, width, xwidth;
1257295610fSBaptiste Daroussin 	int			 done, icol, maxcol, necol, nxcol, quirkcol;
12661d06d6bSBaptiste Daroussin 
12761d06d6bSBaptiste Daroussin 	/*
12861d06d6bSBaptiste Daroussin 	 * Allocate the master column specifiers.  These will hold the
12961d06d6bSBaptiste Daroussin 	 * widths and decimal positions for all cells in the column.  It
13061d06d6bSBaptiste Daroussin 	 * must be freed and nullified by the caller.
13161d06d6bSBaptiste Daroussin 	 */
13261d06d6bSBaptiste Daroussin 
1337295610fSBaptiste Daroussin 	assert(tbl->cols == NULL);
1347295610fSBaptiste Daroussin 	tbl->cols = mandoc_calloc((size_t)sp_first->opts->cols,
13561d06d6bSBaptiste Daroussin 	    sizeof(struct roffcol));
1367295610fSBaptiste Daroussin 	opts = sp_first->opts;
13761d06d6bSBaptiste Daroussin 
1387295610fSBaptiste Daroussin 	maxcol = -1;
1397295610fSBaptiste Daroussin 	first_group = NULL;
1407295610fSBaptiste Daroussin 	for (sp = sp_first; sp != NULL; sp = sp->next) {
1417295610fSBaptiste Daroussin 		if (sp->pos != TBL_SPAN_DATA)
14261d06d6bSBaptiste Daroussin 			continue;
1437295610fSBaptiste Daroussin 
14461d06d6bSBaptiste Daroussin 		/*
14561d06d6bSBaptiste Daroussin 		 * Account for the data cells in the layout, matching it
14661d06d6bSBaptiste Daroussin 		 * to data cells in the data section.
14761d06d6bSBaptiste Daroussin 		 */
1487295610fSBaptiste Daroussin 
1497295610fSBaptiste Daroussin 		gp = &first_group;
1507295610fSBaptiste Daroussin 		for (dp = sp->first; dp != NULL; dp = dp->next) {
15161d06d6bSBaptiste Daroussin 			icol = dp->layout->col;
152*45a5aec3SBaptiste Daroussin 			while (maxcol < icol + dp->hspans)
15361d06d6bSBaptiste Daroussin 				tbl->cols[++maxcol].spacing = SIZE_MAX;
15461d06d6bSBaptiste Daroussin 			col = tbl->cols + icol;
15561d06d6bSBaptiste Daroussin 			col->flags |= dp->layout->flags;
15661d06d6bSBaptiste Daroussin 			if (dp->layout->flags & TBL_CELL_WIGN)
15761d06d6bSBaptiste Daroussin 				continue;
1587295610fSBaptiste Daroussin 
1597295610fSBaptiste Daroussin 			/* Handle explicit width specifications. */
1607295610fSBaptiste Daroussin 
16161d06d6bSBaptiste Daroussin 			if (dp->layout->wstr != NULL &&
16261d06d6bSBaptiste Daroussin 			    dp->layout->width == 0 &&
16361d06d6bSBaptiste Daroussin 			    a2roffsu(dp->layout->wstr, &su, SCALE_EN)
16461d06d6bSBaptiste Daroussin 			    != NULL)
16561d06d6bSBaptiste Daroussin 				dp->layout->width =
16661d06d6bSBaptiste Daroussin 				    (*tbl->sulen)(&su, tbl->arg);
16761d06d6bSBaptiste Daroussin 			if (col->width < dp->layout->width)
16861d06d6bSBaptiste Daroussin 				col->width = dp->layout->width;
16961d06d6bSBaptiste Daroussin 			if (dp->layout->spacing != SIZE_MAX &&
17061d06d6bSBaptiste Daroussin 			    (col->spacing == SIZE_MAX ||
17161d06d6bSBaptiste Daroussin 			     col->spacing < dp->layout->spacing))
17261d06d6bSBaptiste Daroussin 				col->spacing = dp->layout->spacing;
1737295610fSBaptiste Daroussin 
1747295610fSBaptiste Daroussin 			/*
1757295610fSBaptiste Daroussin 			 * Calculate an automatic width.
1767295610fSBaptiste Daroussin 			 * Except for spanning cells, apply it.
1777295610fSBaptiste Daroussin 			 */
1787295610fSBaptiste Daroussin 
1797295610fSBaptiste Daroussin 			width = tblcalc_data(tbl,
1807295610fSBaptiste Daroussin 			    dp->hspans == 0 ? col : NULL,
1817295610fSBaptiste Daroussin 			    opts, dp,
18261d06d6bSBaptiste Daroussin 			    dp->block == 0 ? 0 :
18361d06d6bSBaptiste Daroussin 			    dp->layout->width ? dp->layout->width :
18461d06d6bSBaptiste Daroussin 			    rmargin ? (rmargin + sp->opts->cols / 2)
18561d06d6bSBaptiste Daroussin 			    / (sp->opts->cols + 1) : 0);
1867295610fSBaptiste Daroussin 			if (dp->hspans == 0)
1877295610fSBaptiste Daroussin 				continue;
1887295610fSBaptiste Daroussin 
1897295610fSBaptiste Daroussin 			/*
1907295610fSBaptiste Daroussin 			 * Build an ordered, singly linked list
1917295610fSBaptiste Daroussin 			 * of all groups of columns joined by spans,
1927295610fSBaptiste Daroussin 			 * recording the minimum width for each group.
1937295610fSBaptiste Daroussin 			 */
1947295610fSBaptiste Daroussin 
1957295610fSBaptiste Daroussin 			while (*gp != NULL && ((*gp)->startcol < icol ||
1967295610fSBaptiste Daroussin 			    (*gp)->endcol < icol + dp->hspans))
1977295610fSBaptiste Daroussin 				gp = &(*gp)->next;
1987295610fSBaptiste Daroussin 			if (*gp == NULL || (*gp)->startcol > icol ||
1997295610fSBaptiste Daroussin                             (*gp)->endcol > icol + dp->hspans) {
2007295610fSBaptiste Daroussin 				g = mandoc_malloc(sizeof(*g));
2017295610fSBaptiste Daroussin 				g->next = *gp;
2027295610fSBaptiste Daroussin 				g->wanted = width;
2037295610fSBaptiste Daroussin 				g->startcol = icol;
2047295610fSBaptiste Daroussin 				g->endcol = icol + dp->hspans;
2057295610fSBaptiste Daroussin 				*gp = g;
2067295610fSBaptiste Daroussin 			} else if ((*gp)->wanted < width)
2077295610fSBaptiste Daroussin 				(*gp)->wanted = width;
20861d06d6bSBaptiste Daroussin 		}
20961d06d6bSBaptiste Daroussin 	}
21061d06d6bSBaptiste Daroussin 
21161d06d6bSBaptiste Daroussin 	/*
2127295610fSBaptiste Daroussin 	 * Column spacings are needed for span width calculations,
2137295610fSBaptiste Daroussin 	 * so set the default values now.
2147295610fSBaptiste Daroussin 	 */
2157295610fSBaptiste Daroussin 
2167295610fSBaptiste Daroussin 	for (icol = 0; icol <= maxcol; icol++)
2177295610fSBaptiste Daroussin 		if (tbl->cols[icol].spacing == SIZE_MAX || icol == maxcol)
2187295610fSBaptiste Daroussin 			tbl->cols[icol].spacing = 3;
2197295610fSBaptiste Daroussin 
2207295610fSBaptiste Daroussin 	/*
2217295610fSBaptiste Daroussin 	 * Replace the minimum widths with the missing widths,
2227295610fSBaptiste Daroussin 	 * and dismiss groups that are already wide enough.
2237295610fSBaptiste Daroussin 	 */
2247295610fSBaptiste Daroussin 
2257295610fSBaptiste Daroussin 	gp = &first_group;
2267295610fSBaptiste Daroussin 	while ((g = *gp) != NULL) {
2277295610fSBaptiste Daroussin 		done = 0;
2287295610fSBaptiste Daroussin 		for (icol = g->startcol; icol <= g->endcol; icol++) {
2297295610fSBaptiste Daroussin 			width = tbl->cols[icol].width;
2307295610fSBaptiste Daroussin 			if (icol < g->endcol)
2317295610fSBaptiste Daroussin 				width += tbl->cols[icol].spacing;
2327295610fSBaptiste Daroussin 			if (g->wanted <= width) {
2337295610fSBaptiste Daroussin 				done = 1;
2347295610fSBaptiste Daroussin 				break;
2357295610fSBaptiste Daroussin 			} else
2367295610fSBaptiste Daroussin 				(*gp)->wanted -= width;
2377295610fSBaptiste Daroussin 		}
2387295610fSBaptiste Daroussin 		if (done) {
2397295610fSBaptiste Daroussin 			*gp = g->next;
2407295610fSBaptiste Daroussin 			free(g);
2417295610fSBaptiste Daroussin 		} else
2427295610fSBaptiste Daroussin 			gp = &(*gp)->next;
2437295610fSBaptiste Daroussin 	}
2447295610fSBaptiste Daroussin 
2457295610fSBaptiste Daroussin 	colwidth = mandoc_reallocarray(NULL, maxcol + 1, sizeof(*colwidth));
2467295610fSBaptiste Daroussin 	while (first_group != NULL) {
2477295610fSBaptiste Daroussin 
2487295610fSBaptiste Daroussin 		/*
2497295610fSBaptiste Daroussin 		 * Rebuild the array of the widths of all columns
2507295610fSBaptiste Daroussin 		 * participating in spans that require expansion.
2517295610fSBaptiste Daroussin 		 */
2527295610fSBaptiste Daroussin 
2537295610fSBaptiste Daroussin 		for (icol = 0; icol <= maxcol; icol++)
2547295610fSBaptiste Daroussin 			colwidth[icol] = SIZE_MAX;
2557295610fSBaptiste Daroussin 		for (g = first_group; g != NULL; g = g->next)
2567295610fSBaptiste Daroussin 			for (icol = g->startcol; icol <= g->endcol; icol++)
2577295610fSBaptiste Daroussin 				colwidth[icol] = tbl->cols[icol].width;
2587295610fSBaptiste Daroussin 
2597295610fSBaptiste Daroussin 		/*
2607295610fSBaptiste Daroussin 		 * Find the smallest and second smallest column width
2617295610fSBaptiste Daroussin 		 * among the columns which may need expamsion.
2627295610fSBaptiste Daroussin 		 */
2637295610fSBaptiste Daroussin 
2647295610fSBaptiste Daroussin 		min1 = min2 = SIZE_MAX;
2657295610fSBaptiste Daroussin 		for (icol = 0; icol <= maxcol; icol++) {
2667295610fSBaptiste Daroussin 			if (min1 > colwidth[icol]) {
2677295610fSBaptiste Daroussin 				min2 = min1;
2687295610fSBaptiste Daroussin 				min1 = colwidth[icol];
2697295610fSBaptiste Daroussin 			} else if (min1 < colwidth[icol] &&
2707295610fSBaptiste Daroussin 			    min2 > colwidth[icol])
2717295610fSBaptiste Daroussin 				min2 = colwidth[icol];
2727295610fSBaptiste Daroussin 		}
2737295610fSBaptiste Daroussin 
2747295610fSBaptiste Daroussin 		/*
2757295610fSBaptiste Daroussin 		 * Find the minimum wanted width
2767295610fSBaptiste Daroussin 		 * for any one of the narrowest columns,
2777295610fSBaptiste Daroussin 		 * and mark the columns wanting that width.
2787295610fSBaptiste Daroussin 		 */
2797295610fSBaptiste Daroussin 
2807295610fSBaptiste Daroussin 		wanted = min2;
2817295610fSBaptiste Daroussin 		for (g = first_group; g != NULL; g = g->next) {
2827295610fSBaptiste Daroussin 			necol = 0;
2837295610fSBaptiste Daroussin 			for (icol = g->startcol; icol <= g->endcol; icol++)
2847295610fSBaptiste Daroussin 				if (tbl->cols[icol].width == min1)
2857295610fSBaptiste Daroussin 					necol++;
2867295610fSBaptiste Daroussin 			if (necol == 0)
2877295610fSBaptiste Daroussin 				continue;
2887295610fSBaptiste Daroussin 			width = min1 + (g->wanted - 1) / necol + 1;
2897295610fSBaptiste Daroussin 			if (width > min2)
2907295610fSBaptiste Daroussin 				width = min2;
2917295610fSBaptiste Daroussin 			if (wanted > width)
2927295610fSBaptiste Daroussin 				wanted = width;
2937295610fSBaptiste Daroussin 			for (icol = g->startcol; icol <= g->endcol; icol++)
2947295610fSBaptiste Daroussin 				if (colwidth[icol] == min1 ||
2957295610fSBaptiste Daroussin 				    (colwidth[icol] < min2 &&
2967295610fSBaptiste Daroussin 				     colwidth[icol] > width))
2977295610fSBaptiste Daroussin 					colwidth[icol] = width;
2987295610fSBaptiste Daroussin 		}
2997295610fSBaptiste Daroussin 
3007295610fSBaptiste Daroussin 		/* Record the effect of the widening on the group list. */
3017295610fSBaptiste Daroussin 
3027295610fSBaptiste Daroussin 		gp = &first_group;
3037295610fSBaptiste Daroussin 		while ((g = *gp) != NULL) {
3047295610fSBaptiste Daroussin 			done = 0;
3057295610fSBaptiste Daroussin 			for (icol = g->startcol; icol <= g->endcol; icol++) {
3067295610fSBaptiste Daroussin 				if (colwidth[icol] != wanted ||
3077295610fSBaptiste Daroussin 				    tbl->cols[icol].width == wanted)
3087295610fSBaptiste Daroussin 					continue;
3097295610fSBaptiste Daroussin 				if (g->wanted <= wanted - min1) {
3107295610fSBaptiste Daroussin 					done = 1;
3117295610fSBaptiste Daroussin 					break;
3127295610fSBaptiste Daroussin 				}
3137295610fSBaptiste Daroussin 				g->wanted -= wanted - min1;
3147295610fSBaptiste Daroussin 			}
3157295610fSBaptiste Daroussin 			if (done) {
3167295610fSBaptiste Daroussin 				*gp = g->next;
3177295610fSBaptiste Daroussin 				free(g);
3187295610fSBaptiste Daroussin 			} else
3197295610fSBaptiste Daroussin 				gp = &(*gp)->next;
3207295610fSBaptiste Daroussin 		}
3217295610fSBaptiste Daroussin 
3227295610fSBaptiste Daroussin 		/* Record the effect of the widening on the columns. */
3237295610fSBaptiste Daroussin 
3247295610fSBaptiste Daroussin 		for (icol = 0; icol <= maxcol; icol++)
3257295610fSBaptiste Daroussin 			if (colwidth[icol] == wanted)
3267295610fSBaptiste Daroussin 				tbl->cols[icol].width = wanted;
3277295610fSBaptiste Daroussin 	}
3287295610fSBaptiste Daroussin 	free(colwidth);
3297295610fSBaptiste Daroussin 
3307295610fSBaptiste Daroussin 	/*
3317295610fSBaptiste Daroussin 	 * Align numbers with text.
33261d06d6bSBaptiste Daroussin 	 * Count columns to equalize and columns to maximize.
33361d06d6bSBaptiste Daroussin 	 * Find maximum width of the columns to equalize.
33461d06d6bSBaptiste Daroussin 	 * Find total width of the columns *not* to maximize.
33561d06d6bSBaptiste Daroussin 	 */
33661d06d6bSBaptiste Daroussin 
33761d06d6bSBaptiste Daroussin 	necol = nxcol = 0;
33861d06d6bSBaptiste Daroussin 	ewidth = xwidth = 0;
33961d06d6bSBaptiste Daroussin 	for (icol = 0; icol <= maxcol; icol++) {
34061d06d6bSBaptiste Daroussin 		col = tbl->cols + icol;
3417295610fSBaptiste Daroussin 		if (col->width > col->nwidth)
3427295610fSBaptiste Daroussin 			col->decimal += (col->width - col->nwidth) / 2;
3437295610fSBaptiste Daroussin 		else
3447295610fSBaptiste Daroussin 			col->width = col->nwidth;
34561d06d6bSBaptiste Daroussin 		if (col->flags & TBL_CELL_EQUAL) {
34661d06d6bSBaptiste Daroussin 			necol++;
34761d06d6bSBaptiste Daroussin 			if (ewidth < col->width)
34861d06d6bSBaptiste Daroussin 				ewidth = col->width;
34961d06d6bSBaptiste Daroussin 		}
35061d06d6bSBaptiste Daroussin 		if (col->flags & TBL_CELL_WMAX)
35161d06d6bSBaptiste Daroussin 			nxcol++;
35261d06d6bSBaptiste Daroussin 		else
35361d06d6bSBaptiste Daroussin 			xwidth += col->width;
35461d06d6bSBaptiste Daroussin 	}
35561d06d6bSBaptiste Daroussin 
35661d06d6bSBaptiste Daroussin 	/*
35761d06d6bSBaptiste Daroussin 	 * Equalize columns, if requested for any of them.
35861d06d6bSBaptiste Daroussin 	 * Update total width of the columns not to maximize.
35961d06d6bSBaptiste Daroussin 	 */
36061d06d6bSBaptiste Daroussin 
36161d06d6bSBaptiste Daroussin 	if (necol) {
36261d06d6bSBaptiste Daroussin 		for (icol = 0; icol <= maxcol; icol++) {
36361d06d6bSBaptiste Daroussin 			col = tbl->cols + icol;
36461d06d6bSBaptiste Daroussin 			if ( ! (col->flags & TBL_CELL_EQUAL))
36561d06d6bSBaptiste Daroussin 				continue;
36661d06d6bSBaptiste Daroussin 			if (col->width == ewidth)
36761d06d6bSBaptiste Daroussin 				continue;
36861d06d6bSBaptiste Daroussin 			if (nxcol && rmargin)
36961d06d6bSBaptiste Daroussin 				xwidth += ewidth - col->width;
37061d06d6bSBaptiste Daroussin 			col->width = ewidth;
37161d06d6bSBaptiste Daroussin 		}
37261d06d6bSBaptiste Daroussin 	}
37361d06d6bSBaptiste Daroussin 
37461d06d6bSBaptiste Daroussin 	/*
37561d06d6bSBaptiste Daroussin 	 * If there are any columns to maximize, find the total
37661d06d6bSBaptiste Daroussin 	 * available width, deducting 3n margins between columns.
37761d06d6bSBaptiste Daroussin 	 * Distribute the available width evenly.
37861d06d6bSBaptiste Daroussin 	 */
37961d06d6bSBaptiste Daroussin 
38061d06d6bSBaptiste Daroussin 	if (nxcol && rmargin) {
38161d06d6bSBaptiste Daroussin 		xwidth += 3*maxcol +
38261d06d6bSBaptiste Daroussin 		    (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ?
38361d06d6bSBaptiste Daroussin 		     2 : !!opts->lvert + !!opts->rvert);
38461d06d6bSBaptiste Daroussin 		if (rmargin <= offset + xwidth)
38561d06d6bSBaptiste Daroussin 			return;
38661d06d6bSBaptiste Daroussin 		xwidth = rmargin - offset - xwidth;
38761d06d6bSBaptiste Daroussin 
38861d06d6bSBaptiste Daroussin 		/*
38961d06d6bSBaptiste Daroussin 		 * Emulate a bug in GNU tbl width calculation that
39061d06d6bSBaptiste Daroussin 		 * manifests itself for large numbers of x-columns.
39161d06d6bSBaptiste Daroussin 		 * Emulating it for 5 x-columns gives identical
39261d06d6bSBaptiste Daroussin 		 * behaviour for up to 6 x-columns.
39361d06d6bSBaptiste Daroussin 		 */
39461d06d6bSBaptiste Daroussin 
39561d06d6bSBaptiste Daroussin 		if (nxcol == 5) {
39661d06d6bSBaptiste Daroussin 			quirkcol = xwidth % nxcol + 2;
39761d06d6bSBaptiste Daroussin 			if (quirkcol != 3 && quirkcol != 4)
39861d06d6bSBaptiste Daroussin 				quirkcol = -1;
39961d06d6bSBaptiste Daroussin 		} else
40061d06d6bSBaptiste Daroussin 			quirkcol = -1;
40161d06d6bSBaptiste Daroussin 
40261d06d6bSBaptiste Daroussin 		necol = 0;
40361d06d6bSBaptiste Daroussin 		ewidth = 0;
40461d06d6bSBaptiste Daroussin 		for (icol = 0; icol <= maxcol; icol++) {
40561d06d6bSBaptiste Daroussin 			col = tbl->cols + icol;
40661d06d6bSBaptiste Daroussin 			if ( ! (col->flags & TBL_CELL_WMAX))
40761d06d6bSBaptiste Daroussin 				continue;
40861d06d6bSBaptiste Daroussin 			col->width = (double)xwidth * ++necol / nxcol
40961d06d6bSBaptiste Daroussin 			    - ewidth + 0.4995;
41061d06d6bSBaptiste Daroussin 			if (necol == quirkcol)
41161d06d6bSBaptiste Daroussin 				col->width--;
41261d06d6bSBaptiste Daroussin 			ewidth += col->width;
41361d06d6bSBaptiste Daroussin 		}
41461d06d6bSBaptiste Daroussin 	}
41561d06d6bSBaptiste Daroussin }
41661d06d6bSBaptiste Daroussin 
4177295610fSBaptiste Daroussin static size_t
41861d06d6bSBaptiste Daroussin tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
41961d06d6bSBaptiste Daroussin     const struct tbl_opts *opts, const struct tbl_dat *dp, size_t mw)
42061d06d6bSBaptiste Daroussin {
42161d06d6bSBaptiste Daroussin 	size_t		 sz;
42261d06d6bSBaptiste Daroussin 
42361d06d6bSBaptiste Daroussin 	/* Branch down into data sub-types. */
42461d06d6bSBaptiste Daroussin 
42561d06d6bSBaptiste Daroussin 	switch (dp->layout->pos) {
42661d06d6bSBaptiste Daroussin 	case TBL_CELL_HORIZ:
42761d06d6bSBaptiste Daroussin 	case TBL_CELL_DHORIZ:
42861d06d6bSBaptiste Daroussin 		sz = (*tbl->len)(1, tbl->arg);
4297295610fSBaptiste Daroussin 		if (col != NULL && col->width < sz)
43061d06d6bSBaptiste Daroussin 			col->width = sz;
4317295610fSBaptiste Daroussin 		return sz;
43261d06d6bSBaptiste Daroussin 	case TBL_CELL_LONG:
43361d06d6bSBaptiste Daroussin 	case TBL_CELL_CENTRE:
43461d06d6bSBaptiste Daroussin 	case TBL_CELL_LEFT:
43561d06d6bSBaptiste Daroussin 	case TBL_CELL_RIGHT:
4367295610fSBaptiste Daroussin 		return tblcalc_literal(tbl, col, dp, mw);
43761d06d6bSBaptiste Daroussin 	case TBL_CELL_NUMBER:
4387295610fSBaptiste Daroussin 		return tblcalc_number(tbl, col, opts, dp);
43961d06d6bSBaptiste Daroussin 	case TBL_CELL_DOWN:
4407295610fSBaptiste Daroussin 		return 0;
44161d06d6bSBaptiste Daroussin 	default:
44261d06d6bSBaptiste Daroussin 		abort();
44361d06d6bSBaptiste Daroussin 	}
44461d06d6bSBaptiste Daroussin }
44561d06d6bSBaptiste Daroussin 
4467295610fSBaptiste Daroussin static size_t
44761d06d6bSBaptiste Daroussin tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
44861d06d6bSBaptiste Daroussin     const struct tbl_dat *dp, size_t mw)
44961d06d6bSBaptiste Daroussin {
45061d06d6bSBaptiste Daroussin 	const char	*str;	/* Beginning of the first line. */
45161d06d6bSBaptiste Daroussin 	const char	*beg;	/* Beginning of the current line. */
45261d06d6bSBaptiste Daroussin 	char		*end;	/* End of the current line. */
45361d06d6bSBaptiste Daroussin 	size_t		 lsz;	/* Length of the current line. */
45461d06d6bSBaptiste Daroussin 	size_t		 wsz;	/* Length of the current word. */
4557295610fSBaptiste Daroussin 	size_t		 msz;   /* Length of the longest line. */
45661d06d6bSBaptiste Daroussin 
45761d06d6bSBaptiste Daroussin 	if (dp->string == NULL || *dp->string == '\0')
4587295610fSBaptiste Daroussin 		return 0;
45961d06d6bSBaptiste Daroussin 	str = mw ? mandoc_strdup(dp->string) : dp->string;
4607295610fSBaptiste Daroussin 	msz = lsz = 0;
46161d06d6bSBaptiste Daroussin 	for (beg = str; beg != NULL && *beg != '\0'; beg = end) {
46261d06d6bSBaptiste Daroussin 		end = mw ? strchr(beg, ' ') : NULL;
46361d06d6bSBaptiste Daroussin 		if (end != NULL) {
46461d06d6bSBaptiste Daroussin 			*end++ = '\0';
46561d06d6bSBaptiste Daroussin 			while (*end == ' ')
46661d06d6bSBaptiste Daroussin 				end++;
46761d06d6bSBaptiste Daroussin 		}
46861d06d6bSBaptiste Daroussin 		wsz = (*tbl->slen)(beg, tbl->arg);
46961d06d6bSBaptiste Daroussin 		if (mw && lsz && lsz + 1 + wsz <= mw)
47061d06d6bSBaptiste Daroussin 			lsz += 1 + wsz;
47161d06d6bSBaptiste Daroussin 		else
47261d06d6bSBaptiste Daroussin 			lsz = wsz;
4737295610fSBaptiste Daroussin 		if (msz < lsz)
4747295610fSBaptiste Daroussin 			msz = lsz;
47561d06d6bSBaptiste Daroussin 	}
47661d06d6bSBaptiste Daroussin 	if (mw)
47761d06d6bSBaptiste Daroussin 		free((void *)str);
4787295610fSBaptiste Daroussin 	if (col != NULL && col->width < msz)
4797295610fSBaptiste Daroussin 		col->width = msz;
4807295610fSBaptiste Daroussin 	return msz;
48161d06d6bSBaptiste Daroussin }
48261d06d6bSBaptiste Daroussin 
4837295610fSBaptiste Daroussin static size_t
48461d06d6bSBaptiste Daroussin tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
48561d06d6bSBaptiste Daroussin 		const struct tbl_opts *opts, const struct tbl_dat *dp)
48661d06d6bSBaptiste Daroussin {
4877295610fSBaptiste Daroussin 	const char	*cp, *lastdigit, *lastpoint;
4887295610fSBaptiste Daroussin 	size_t		 intsz, totsz;
48961d06d6bSBaptiste Daroussin 	char		 buf[2];
49061d06d6bSBaptiste Daroussin 
4917295610fSBaptiste Daroussin 	if (dp->string == NULL || *dp->string == '\0')
4927295610fSBaptiste Daroussin 		return 0;
4937295610fSBaptiste Daroussin 
4947295610fSBaptiste Daroussin 	totsz = (*tbl->slen)(dp->string, tbl->arg);
4957295610fSBaptiste Daroussin 	if (col == NULL)
4967295610fSBaptiste Daroussin 		return totsz;
4977295610fSBaptiste Daroussin 
49861d06d6bSBaptiste Daroussin 	/*
4997295610fSBaptiste Daroussin 	 * Find the last digit and
5007295610fSBaptiste Daroussin 	 * the last decimal point that is adjacent to a digit.
5017295610fSBaptiste Daroussin 	 * The alignment indicator "\&" overrides everything.
50261d06d6bSBaptiste Daroussin 	 */
50361d06d6bSBaptiste Daroussin 
5047295610fSBaptiste Daroussin 	lastdigit = lastpoint = NULL;
5057295610fSBaptiste Daroussin 	for (cp = dp->string; cp[0] != '\0'; cp++) {
5067295610fSBaptiste Daroussin 		if (cp[0] == '\\' && cp[1] == '&') {
5077295610fSBaptiste Daroussin 			lastdigit = lastpoint = cp;
5087295610fSBaptiste Daroussin 			break;
5097295610fSBaptiste Daroussin 		} else if (cp[0] == opts->decimal &&
5107295610fSBaptiste Daroussin 		    (isdigit((unsigned char)cp[1]) ||
5117295610fSBaptiste Daroussin 		     (cp > dp->string && isdigit((unsigned char)cp[-1]))))
5127295610fSBaptiste Daroussin 			lastpoint = cp;
5137295610fSBaptiste Daroussin 		else if (isdigit((unsigned char)cp[0]))
5147295610fSBaptiste Daroussin 			lastdigit = cp;
51561d06d6bSBaptiste Daroussin 	}
5167295610fSBaptiste Daroussin 
5177295610fSBaptiste Daroussin 	/* Not a number, treat as a literal string. */
5187295610fSBaptiste Daroussin 
5197295610fSBaptiste Daroussin 	if (lastdigit == NULL) {
5207295610fSBaptiste Daroussin 		if (col != NULL && col->width < totsz)
5217295610fSBaptiste Daroussin 			col->width = totsz;
5227295610fSBaptiste Daroussin 		return totsz;
5237295610fSBaptiste Daroussin 	}
5247295610fSBaptiste Daroussin 
5257295610fSBaptiste Daroussin 	/* Measure the width of the integer part. */
5267295610fSBaptiste Daroussin 
5277295610fSBaptiste Daroussin 	if (lastpoint == NULL)
5287295610fSBaptiste Daroussin 		lastpoint = lastdigit + 1;
5297295610fSBaptiste Daroussin 	intsz = 0;
5307295610fSBaptiste Daroussin 	buf[1] = '\0';
5317295610fSBaptiste Daroussin 	for (cp = dp->string; cp < lastpoint; cp++) {
5327295610fSBaptiste Daroussin 		buf[0] = cp[0];
5337295610fSBaptiste Daroussin 		intsz += (*tbl->slen)(buf, tbl->arg);
5347295610fSBaptiste Daroussin 	}
5357295610fSBaptiste Daroussin 
5367295610fSBaptiste Daroussin 	/*
5377295610fSBaptiste Daroussin          * If this number has more integer digits than all numbers
5387295610fSBaptiste Daroussin          * seen on earlier lines, shift them all to the right.
5397295610fSBaptiste Daroussin 	 * If it has fewer, shift this number to the right.
5407295610fSBaptiste Daroussin 	 */
5417295610fSBaptiste Daroussin 
5427295610fSBaptiste Daroussin 	if (intsz > col->decimal) {
5437295610fSBaptiste Daroussin 		col->nwidth += intsz - col->decimal;
5447295610fSBaptiste Daroussin 		col->decimal = intsz;
54561d06d6bSBaptiste Daroussin 	} else
5467295610fSBaptiste Daroussin 		totsz += col->decimal - intsz;
54761d06d6bSBaptiste Daroussin 
5487295610fSBaptiste Daroussin 	/* Update the maximum total width seen so far. */
54961d06d6bSBaptiste Daroussin 
5507295610fSBaptiste Daroussin 	if (totsz > col->nwidth)
5517295610fSBaptiste Daroussin 		col->nwidth = totsz;
5527295610fSBaptiste Daroussin 	return totsz;
55361d06d6bSBaptiste Daroussin }
554