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