xref: /netbsd-src/external/bsd/mdocml/dist/out.c (revision e7cf62c1813f94dc3c0469542fa9f1ac17f105ef)
16167eca2Schristos /*	Id: out.c,v 1.77 2018/12/13 11:55:47 schwarze Exp  */
24154958bSjoerg /*
348741257Sjoerg  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
46167eca2Schristos  * Copyright (c) 2011,2014,2015,2017,2018 Ingo Schwarze <schwarze@openbsd.org>
54154958bSjoerg  *
64154958bSjoerg  * Permission to use, copy, modify, and distribute this software for any
74154958bSjoerg  * purpose with or without fee is hereby granted, provided that the above
84154958bSjoerg  * copyright notice and this permission notice appear in all copies.
94154958bSjoerg  *
104154958bSjoerg  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
114154958bSjoerg  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
124154958bSjoerg  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
134154958bSjoerg  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
144154958bSjoerg  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
154154958bSjoerg  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
164154958bSjoerg  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
174154958bSjoerg  */
18d5e63c8dSjoerg #include "config.h"
19d5e63c8dSjoerg 
204154958bSjoerg #include <sys/types.h>
214154958bSjoerg 
2222af4063Sjoerg #include <assert.h>
236167eca2Schristos #include <ctype.h>
24c9bcef03Schristos #include <stdint.h>
254154958bSjoerg #include <stdlib.h>
2622af4063Sjoerg #include <string.h>
2722af4063Sjoerg #include <time.h>
284154958bSjoerg 
29fec65c98Schristos #include "mandoc_aux.h"
306167eca2Schristos #include "tbl.h"
314154958bSjoerg #include "out.h"
324154958bSjoerg 
336167eca2Schristos struct	tbl_colgroup {
346167eca2Schristos 	struct tbl_colgroup	*next;
356167eca2Schristos 	size_t			 wanted;
366167eca2Schristos 	int			 startcol;
376167eca2Schristos 	int			 endcol;
386167eca2Schristos };
396167eca2Schristos 
406167eca2Schristos static	size_t	tblcalc_data(struct rofftbl *, struct roffcol *,
41c9bcef03Schristos 			const struct tbl_opts *, const struct tbl_dat *,
42c9bcef03Schristos 			size_t);
436167eca2Schristos static	size_t	tblcalc_literal(struct rofftbl *, struct roffcol *,
44c9bcef03Schristos 			const struct tbl_dat *, size_t);
456167eca2Schristos static	size_t	tblcalc_number(struct rofftbl *, struct roffcol *,
4670f041f9Sjoerg 			const struct tbl_opts *, const struct tbl_dat *);
47c0d9444aSjoerg 
48fec65c98Schristos 
494154958bSjoerg /*
50fec65c98Schristos  * Parse the *src string and store a scaling unit into *dst.
51fec65c98Schristos  * If the string doesn't specify the unit, use the default.
52fec65c98Schristos  * If no default is specified, fail.
53c9bcef03Schristos  * Return a pointer to the byte after the last byte used,
54c9bcef03Schristos  * or NULL on total failure.
554154958bSjoerg  */
56c9bcef03Schristos const char *
a2roffsu(const char * src,struct roffsu * dst,enum roffscale def)574154958bSjoerg a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
584154958bSjoerg {
59fec65c98Schristos 	char		*endptr;
604154958bSjoerg 
61fec65c98Schristos 	dst->unit = def == SCALE_MAX ? SCALE_BU : def;
62fec65c98Schristos 	dst->scale = strtod(src, &endptr);
63fec65c98Schristos 	if (endptr == src)
64c9bcef03Schristos 		return NULL;
654154958bSjoerg 
66fec65c98Schristos 	switch (*endptr++) {
67fec65c98Schristos 	case 'c':
68fec65c98Schristos 		dst->unit = SCALE_CM;
694154958bSjoerg 		break;
70fec65c98Schristos 	case 'i':
71fec65c98Schristos 		dst->unit = SCALE_IN;
724154958bSjoerg 		break;
73fec65c98Schristos 	case 'f':
74fec65c98Schristos 		dst->unit = SCALE_FS;
75fec65c98Schristos 		break;
76fec65c98Schristos 	case 'M':
77fec65c98Schristos 		dst->unit = SCALE_MM;
78fec65c98Schristos 		break;
79fec65c98Schristos 	case 'm':
80fec65c98Schristos 		dst->unit = SCALE_EM;
81fec65c98Schristos 		break;
82fec65c98Schristos 	case 'n':
83fec65c98Schristos 		dst->unit = SCALE_EN;
84fec65c98Schristos 		break;
85fec65c98Schristos 	case 'P':
86fec65c98Schristos 		dst->unit = SCALE_PC;
87fec65c98Schristos 		break;
88fec65c98Schristos 	case 'p':
89fec65c98Schristos 		dst->unit = SCALE_PT;
90fec65c98Schristos 		break;
91fec65c98Schristos 	case 'u':
92fec65c98Schristos 		dst->unit = SCALE_BU;
93fec65c98Schristos 		break;
94fec65c98Schristos 	case 'v':
95fec65c98Schristos 		dst->unit = SCALE_VS;
96fec65c98Schristos 		break;
974154958bSjoerg 	default:
98c9bcef03Schristos 		endptr--;
994154958bSjoerg 		if (SCALE_MAX == def)
100c9bcef03Schristos 			return NULL;
101fec65c98Schristos 		dst->unit = def;
1024154958bSjoerg 		break;
1034154958bSjoerg 	}
104c9bcef03Schristos 	return endptr;
1054154958bSjoerg }
10622af4063Sjoerg 
107c0d9444aSjoerg /*
108c0d9444aSjoerg  * Calculate the abstract widths and decimal positions of columns in a
109c0d9444aSjoerg  * table.  This routine allocates the columns structures then runs over
110c0d9444aSjoerg  * all rows and cells in the table.  The function pointers in "tbl" are
111c0d9444aSjoerg  * used for the actual width calculations.
112c0d9444aSjoerg  */
113c0d9444aSjoerg void
tblcalc(struct rofftbl * tbl,const struct tbl_span * sp_first,size_t offset,size_t rmargin)1146167eca2Schristos tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first,
115c9bcef03Schristos     size_t offset, size_t rmargin)
116c0d9444aSjoerg {
117c9bcef03Schristos 	struct roffsu		 su;
118fec65c98Schristos 	const struct tbl_opts	*opts;
1196167eca2Schristos 	const struct tbl_span	*sp;
120c0d9444aSjoerg 	const struct tbl_dat	*dp;
121c0d9444aSjoerg 	struct roffcol		*col;
1226167eca2Schristos 	struct tbl_colgroup	*first_group, **gp, *g;
1236167eca2Schristos 	size_t			*colwidth;
1246167eca2Schristos 	size_t			 ewidth, min1, min2, wanted, width, xwidth;
1256167eca2Schristos 	int			 done, icol, maxcol, necol, nxcol, quirkcol;
126c0d9444aSjoerg 
127c0d9444aSjoerg 	/*
128c0d9444aSjoerg 	 * Allocate the master column specifiers.  These will hold the
129c0d9444aSjoerg 	 * widths and decimal positions for all cells in the column.  It
130c0d9444aSjoerg 	 * must be freed and nullified by the caller.
131c0d9444aSjoerg 	 */
132c0d9444aSjoerg 
1336167eca2Schristos 	assert(tbl->cols == NULL);
1346167eca2Schristos 	tbl->cols = mandoc_calloc((size_t)sp_first->opts->cols,
135fec65c98Schristos 	    sizeof(struct roffcol));
1366167eca2Schristos 	opts = sp_first->opts;
137c0d9444aSjoerg 
1386167eca2Schristos 	maxcol = -1;
1396167eca2Schristos 	first_group = NULL;
1406167eca2Schristos 	for (sp = sp_first; sp != NULL; sp = sp->next) {
1416167eca2Schristos 		if (sp->pos != TBL_SPAN_DATA)
142c0d9444aSjoerg 			continue;
1436167eca2Schristos 
144c0d9444aSjoerg 		/*
145c0d9444aSjoerg 		 * Account for the data cells in the layout, matching it
146c0d9444aSjoerg 		 * to data cells in the data section.
147c0d9444aSjoerg 		 */
1486167eca2Schristos 
1496167eca2Schristos 		gp = &first_group;
1506167eca2Schristos 		for (dp = sp->first; dp != NULL; dp = dp->next) {
151fec65c98Schristos 			icol = dp->layout->col;
152*e7cf62c1Schristos 			while (maxcol < icol + dp->hspans)
153c9bcef03Schristos 				tbl->cols[++maxcol].spacing = SIZE_MAX;
154fec65c98Schristos 			col = tbl->cols + icol;
155fec65c98Schristos 			col->flags |= dp->layout->flags;
156fec65c98Schristos 			if (dp->layout->flags & TBL_CELL_WIGN)
157fec65c98Schristos 				continue;
1586167eca2Schristos 
1596167eca2Schristos 			/* Handle explicit width specifications. */
1606167eca2Schristos 
161c9bcef03Schristos 			if (dp->layout->wstr != NULL &&
162c9bcef03Schristos 			    dp->layout->width == 0 &&
163c9bcef03Schristos 			    a2roffsu(dp->layout->wstr, &su, SCALE_EN)
164c9bcef03Schristos 			    != NULL)
165c9bcef03Schristos 				dp->layout->width =
166c9bcef03Schristos 				    (*tbl->sulen)(&su, tbl->arg);
167c9bcef03Schristos 			if (col->width < dp->layout->width)
168c9bcef03Schristos 				col->width = dp->layout->width;
169c9bcef03Schristos 			if (dp->layout->spacing != SIZE_MAX &&
170c9bcef03Schristos 			    (col->spacing == SIZE_MAX ||
171c9bcef03Schristos 			     col->spacing < dp->layout->spacing))
172c9bcef03Schristos 				col->spacing = dp->layout->spacing;
1736167eca2Schristos 
1746167eca2Schristos 			/*
1756167eca2Schristos 			 * Calculate an automatic width.
1766167eca2Schristos 			 * Except for spanning cells, apply it.
1776167eca2Schristos 			 */
1786167eca2Schristos 
1796167eca2Schristos 			width = tblcalc_data(tbl,
1806167eca2Schristos 			    dp->hspans == 0 ? col : NULL,
1816167eca2Schristos 			    opts, dp,
182c9bcef03Schristos 			    dp->block == 0 ? 0 :
183c9bcef03Schristos 			    dp->layout->width ? dp->layout->width :
184c9bcef03Schristos 			    rmargin ? (rmargin + sp->opts->cols / 2)
185c9bcef03Schristos 			    / (sp->opts->cols + 1) : 0);
1866167eca2Schristos 			if (dp->hspans == 0)
1876167eca2Schristos 				continue;
1886167eca2Schristos 
1896167eca2Schristos 			/*
1906167eca2Schristos 			 * Build an ordered, singly linked list
1916167eca2Schristos 			 * of all groups of columns joined by spans,
1926167eca2Schristos 			 * recording the minimum width for each group.
1936167eca2Schristos 			 */
1946167eca2Schristos 
1956167eca2Schristos 			while (*gp != NULL && ((*gp)->startcol < icol ||
1966167eca2Schristos 			    (*gp)->endcol < icol + dp->hspans))
1976167eca2Schristos 				gp = &(*gp)->next;
1986167eca2Schristos 			if (*gp == NULL || (*gp)->startcol > icol ||
1996167eca2Schristos                             (*gp)->endcol > icol + dp->hspans) {
2006167eca2Schristos 				g = mandoc_malloc(sizeof(*g));
2016167eca2Schristos 				g->next = *gp;
2026167eca2Schristos 				g->wanted = width;
2036167eca2Schristos 				g->startcol = icol;
2046167eca2Schristos 				g->endcol = icol + dp->hspans;
2056167eca2Schristos 				*gp = g;
2066167eca2Schristos 			} else if ((*gp)->wanted < width)
2076167eca2Schristos 				(*gp)->wanted = width;
208fec65c98Schristos 		}
209fec65c98Schristos 	}
210fec65c98Schristos 
211fec65c98Schristos 	/*
2126167eca2Schristos 	 * Column spacings are needed for span width calculations,
2136167eca2Schristos 	 * so set the default values now.
2146167eca2Schristos 	 */
2156167eca2Schristos 
2166167eca2Schristos 	for (icol = 0; icol <= maxcol; icol++)
2176167eca2Schristos 		if (tbl->cols[icol].spacing == SIZE_MAX || icol == maxcol)
2186167eca2Schristos 			tbl->cols[icol].spacing = 3;
2196167eca2Schristos 
2206167eca2Schristos 	/*
2216167eca2Schristos 	 * Replace the minimum widths with the missing widths,
2226167eca2Schristos 	 * and dismiss groups that are already wide enough.
2236167eca2Schristos 	 */
2246167eca2Schristos 
2256167eca2Schristos 	gp = &first_group;
2266167eca2Schristos 	while ((g = *gp) != NULL) {
2276167eca2Schristos 		done = 0;
2286167eca2Schristos 		for (icol = g->startcol; icol <= g->endcol; icol++) {
2296167eca2Schristos 			width = tbl->cols[icol].width;
2306167eca2Schristos 			if (icol < g->endcol)
2316167eca2Schristos 				width += tbl->cols[icol].spacing;
2326167eca2Schristos 			if (g->wanted <= width) {
2336167eca2Schristos 				done = 1;
2346167eca2Schristos 				break;
2356167eca2Schristos 			} else
2366167eca2Schristos 				(*gp)->wanted -= width;
2376167eca2Schristos 		}
2386167eca2Schristos 		if (done) {
2396167eca2Schristos 			*gp = g->next;
2406167eca2Schristos 			free(g);
2416167eca2Schristos 		} else
2426167eca2Schristos 			gp = &(*gp)->next;
2436167eca2Schristos 	}
2446167eca2Schristos 
2456167eca2Schristos 	colwidth = mandoc_reallocarray(NULL, maxcol + 1, sizeof(*colwidth));
2466167eca2Schristos 	while (first_group != NULL) {
2476167eca2Schristos 
2486167eca2Schristos 		/*
2496167eca2Schristos 		 * Rebuild the array of the widths of all columns
2506167eca2Schristos 		 * participating in spans that require expansion.
2516167eca2Schristos 		 */
2526167eca2Schristos 
2536167eca2Schristos 		for (icol = 0; icol <= maxcol; icol++)
2546167eca2Schristos 			colwidth[icol] = SIZE_MAX;
2556167eca2Schristos 		for (g = first_group; g != NULL; g = g->next)
2566167eca2Schristos 			for (icol = g->startcol; icol <= g->endcol; icol++)
2576167eca2Schristos 				colwidth[icol] = tbl->cols[icol].width;
2586167eca2Schristos 
2596167eca2Schristos 		/*
2606167eca2Schristos 		 * Find the smallest and second smallest column width
2616167eca2Schristos 		 * among the columns which may need expamsion.
2626167eca2Schristos 		 */
2636167eca2Schristos 
2646167eca2Schristos 		min1 = min2 = SIZE_MAX;
2656167eca2Schristos 		for (icol = 0; icol <= maxcol; icol++) {
2666167eca2Schristos 			if (min1 > colwidth[icol]) {
2676167eca2Schristos 				min2 = min1;
2686167eca2Schristos 				min1 = colwidth[icol];
2696167eca2Schristos 			} else if (min1 < colwidth[icol] &&
2706167eca2Schristos 			    min2 > colwidth[icol])
2716167eca2Schristos 				min2 = colwidth[icol];
2726167eca2Schristos 		}
2736167eca2Schristos 
2746167eca2Schristos 		/*
2756167eca2Schristos 		 * Find the minimum wanted width
2766167eca2Schristos 		 * for any one of the narrowest columns,
2776167eca2Schristos 		 * and mark the columns wanting that width.
2786167eca2Schristos 		 */
2796167eca2Schristos 
2806167eca2Schristos 		wanted = min2;
2816167eca2Schristos 		for (g = first_group; g != NULL; g = g->next) {
2826167eca2Schristos 			necol = 0;
2836167eca2Schristos 			for (icol = g->startcol; icol <= g->endcol; icol++)
2846167eca2Schristos 				if (tbl->cols[icol].width == min1)
2856167eca2Schristos 					necol++;
2866167eca2Schristos 			if (necol == 0)
2876167eca2Schristos 				continue;
2886167eca2Schristos 			width = min1 + (g->wanted - 1) / necol + 1;
2896167eca2Schristos 			if (width > min2)
2906167eca2Schristos 				width = min2;
2916167eca2Schristos 			if (wanted > width)
2926167eca2Schristos 				wanted = width;
2936167eca2Schristos 			for (icol = g->startcol; icol <= g->endcol; icol++)
2946167eca2Schristos 				if (colwidth[icol] == min1 ||
2956167eca2Schristos 				    (colwidth[icol] < min2 &&
2966167eca2Schristos 				     colwidth[icol] > width))
2976167eca2Schristos 					colwidth[icol] = width;
2986167eca2Schristos 		}
2996167eca2Schristos 
3006167eca2Schristos 		/* Record the effect of the widening on the group list. */
3016167eca2Schristos 
3026167eca2Schristos 		gp = &first_group;
3036167eca2Schristos 		while ((g = *gp) != NULL) {
3046167eca2Schristos 			done = 0;
3056167eca2Schristos 			for (icol = g->startcol; icol <= g->endcol; icol++) {
3066167eca2Schristos 				if (colwidth[icol] != wanted ||
3076167eca2Schristos 				    tbl->cols[icol].width == wanted)
3086167eca2Schristos 					continue;
3096167eca2Schristos 				if (g->wanted <= wanted - min1) {
3106167eca2Schristos 					done = 1;
3116167eca2Schristos 					break;
3126167eca2Schristos 				}
3136167eca2Schristos 				g->wanted -= wanted - min1;
3146167eca2Schristos 			}
3156167eca2Schristos 			if (done) {
3166167eca2Schristos 				*gp = g->next;
3176167eca2Schristos 				free(g);
3186167eca2Schristos 			} else
3196167eca2Schristos 				gp = &(*gp)->next;
3206167eca2Schristos 		}
3216167eca2Schristos 
3226167eca2Schristos 		/* Record the effect of the widening on the columns. */
3236167eca2Schristos 
3246167eca2Schristos 		for (icol = 0; icol <= maxcol; icol++)
3256167eca2Schristos 			if (colwidth[icol] == wanted)
3266167eca2Schristos 				tbl->cols[icol].width = wanted;
3276167eca2Schristos 	}
3286167eca2Schristos 	free(colwidth);
3296167eca2Schristos 
3306167eca2Schristos 	/*
3316167eca2Schristos 	 * Align numbers with text.
332fec65c98Schristos 	 * Count columns to equalize and columns to maximize.
333fec65c98Schristos 	 * Find maximum width of the columns to equalize.
334fec65c98Schristos 	 * Find total width of the columns *not* to maximize.
335fec65c98Schristos 	 */
336fec65c98Schristos 
337fec65c98Schristos 	necol = nxcol = 0;
338fec65c98Schristos 	ewidth = xwidth = 0;
339fec65c98Schristos 	for (icol = 0; icol <= maxcol; icol++) {
340fec65c98Schristos 		col = tbl->cols + icol;
3416167eca2Schristos 		if (col->width > col->nwidth)
3426167eca2Schristos 			col->decimal += (col->width - col->nwidth) / 2;
3436167eca2Schristos 		else
3446167eca2Schristos 			col->width = col->nwidth;
345fec65c98Schristos 		if (col->flags & TBL_CELL_EQUAL) {
346fec65c98Schristos 			necol++;
347fec65c98Schristos 			if (ewidth < col->width)
348fec65c98Schristos 				ewidth = col->width;
349fec65c98Schristos 		}
350fec65c98Schristos 		if (col->flags & TBL_CELL_WMAX)
351fec65c98Schristos 			nxcol++;
352fec65c98Schristos 		else
353fec65c98Schristos 			xwidth += col->width;
354fec65c98Schristos 	}
355fec65c98Schristos 
356fec65c98Schristos 	/*
357fec65c98Schristos 	 * Equalize columns, if requested for any of them.
358fec65c98Schristos 	 * Update total width of the columns not to maximize.
359fec65c98Schristos 	 */
360fec65c98Schristos 
361fec65c98Schristos 	if (necol) {
362fec65c98Schristos 		for (icol = 0; icol <= maxcol; icol++) {
363fec65c98Schristos 			col = tbl->cols + icol;
364fec65c98Schristos 			if ( ! (col->flags & TBL_CELL_EQUAL))
365fec65c98Schristos 				continue;
366fec65c98Schristos 			if (col->width == ewidth)
367fec65c98Schristos 				continue;
368c9bcef03Schristos 			if (nxcol && rmargin)
369fec65c98Schristos 				xwidth += ewidth - col->width;
370fec65c98Schristos 			col->width = ewidth;
371fec65c98Schristos 		}
372fec65c98Schristos 	}
373fec65c98Schristos 
374fec65c98Schristos 	/*
375fec65c98Schristos 	 * If there are any columns to maximize, find the total
376fec65c98Schristos 	 * available width, deducting 3n margins between columns.
377fec65c98Schristos 	 * Distribute the available width evenly.
378fec65c98Schristos 	 */
379fec65c98Schristos 
380c9bcef03Schristos 	if (nxcol && rmargin) {
381c9bcef03Schristos 		xwidth += 3*maxcol +
382fec65c98Schristos 		    (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ?
383fec65c98Schristos 		     2 : !!opts->lvert + !!opts->rvert);
384c9bcef03Schristos 		if (rmargin <= offset + xwidth)
385c9bcef03Schristos 			return;
386c9bcef03Schristos 		xwidth = rmargin - offset - xwidth;
387fec65c98Schristos 
388fec65c98Schristos 		/*
389fec65c98Schristos 		 * Emulate a bug in GNU tbl width calculation that
390fec65c98Schristos 		 * manifests itself for large numbers of x-columns.
391fec65c98Schristos 		 * Emulating it for 5 x-columns gives identical
392fec65c98Schristos 		 * behaviour for up to 6 x-columns.
393fec65c98Schristos 		 */
394fec65c98Schristos 
395fec65c98Schristos 		if (nxcol == 5) {
396fec65c98Schristos 			quirkcol = xwidth % nxcol + 2;
397fec65c98Schristos 			if (quirkcol != 3 && quirkcol != 4)
398fec65c98Schristos 				quirkcol = -1;
399fec65c98Schristos 		} else
400fec65c98Schristos 			quirkcol = -1;
401fec65c98Schristos 
402fec65c98Schristos 		necol = 0;
403fec65c98Schristos 		ewidth = 0;
404fec65c98Schristos 		for (icol = 0; icol <= maxcol; icol++) {
405fec65c98Schristos 			col = tbl->cols + icol;
406fec65c98Schristos 			if ( ! (col->flags & TBL_CELL_WMAX))
407fec65c98Schristos 				continue;
408fec65c98Schristos 			col->width = (double)xwidth * ++necol / nxcol
409fec65c98Schristos 			    - ewidth + 0.4995;
410fec65c98Schristos 			if (necol == quirkcol)
411fec65c98Schristos 				col->width--;
412fec65c98Schristos 			ewidth += col->width;
413c0d9444aSjoerg 		}
414c0d9444aSjoerg 	}
415c0d9444aSjoerg }
416c0d9444aSjoerg 
4176167eca2Schristos static size_t
tblcalc_data(struct rofftbl * tbl,struct roffcol * col,const struct tbl_opts * opts,const struct tbl_dat * dp,size_t mw)418c0d9444aSjoerg tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
419c9bcef03Schristos     const struct tbl_opts *opts, const struct tbl_dat *dp, size_t mw)
420c0d9444aSjoerg {
421c0d9444aSjoerg 	size_t		 sz;
422c0d9444aSjoerg 
423c0d9444aSjoerg 	/* Branch down into data sub-types. */
424c0d9444aSjoerg 
425c0d9444aSjoerg 	switch (dp->layout->pos) {
426fec65c98Schristos 	case TBL_CELL_HORIZ:
427fec65c98Schristos 	case TBL_CELL_DHORIZ:
428c0d9444aSjoerg 		sz = (*tbl->len)(1, tbl->arg);
4296167eca2Schristos 		if (col != NULL && col->width < sz)
430c0d9444aSjoerg 			col->width = sz;
4316167eca2Schristos 		return sz;
432fec65c98Schristos 	case TBL_CELL_LONG:
433fec65c98Schristos 	case TBL_CELL_CENTRE:
434fec65c98Schristos 	case TBL_CELL_LEFT:
435fec65c98Schristos 	case TBL_CELL_RIGHT:
4366167eca2Schristos 		return tblcalc_literal(tbl, col, dp, mw);
437fec65c98Schristos 	case TBL_CELL_NUMBER:
4386167eca2Schristos 		return tblcalc_number(tbl, col, opts, dp);
439fec65c98Schristos 	case TBL_CELL_DOWN:
4406167eca2Schristos 		return 0;
441c0d9444aSjoerg 	default:
442c0d9444aSjoerg 		abort();
443c0d9444aSjoerg 	}
444c0d9444aSjoerg }
445c0d9444aSjoerg 
4466167eca2Schristos static size_t
tblcalc_literal(struct rofftbl * tbl,struct roffcol * col,const struct tbl_dat * dp,size_t mw)447c0d9444aSjoerg tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
448c9bcef03Schristos     const struct tbl_dat *dp, size_t mw)
449c0d9444aSjoerg {
450c9bcef03Schristos 	const char	*str;	/* Beginning of the first line. */
451c9bcef03Schristos 	const char	*beg;	/* Beginning of the current line. */
452c9bcef03Schristos 	char		*end;	/* End of the current line. */
453c9bcef03Schristos 	size_t		 lsz;	/* Length of the current line. */
454c9bcef03Schristos 	size_t		 wsz;	/* Length of the current word. */
4556167eca2Schristos 	size_t		 msz;   /* Length of the longest line. */
456c0d9444aSjoerg 
457c9bcef03Schristos 	if (dp->string == NULL || *dp->string == '\0')
4586167eca2Schristos 		return 0;
459c9bcef03Schristos 	str = mw ? mandoc_strdup(dp->string) : dp->string;
4606167eca2Schristos 	msz = lsz = 0;
461c9bcef03Schristos 	for (beg = str; beg != NULL && *beg != '\0'; beg = end) {
462c9bcef03Schristos 		end = mw ? strchr(beg, ' ') : NULL;
463c9bcef03Schristos 		if (end != NULL) {
464c9bcef03Schristos 			*end++ = '\0';
465c9bcef03Schristos 			while (*end == ' ')
466c9bcef03Schristos 				end++;
467c9bcef03Schristos 		}
468c9bcef03Schristos 		wsz = (*tbl->slen)(beg, tbl->arg);
469c9bcef03Schristos 		if (mw && lsz && lsz + 1 + wsz <= mw)
470c9bcef03Schristos 			lsz += 1 + wsz;
471c9bcef03Schristos 		else
472c9bcef03Schristos 			lsz = wsz;
4736167eca2Schristos 		if (msz < lsz)
4746167eca2Schristos 			msz = lsz;
475c9bcef03Schristos 	}
476c9bcef03Schristos 	if (mw)
47714e7489eSchristos 		free(__UNCONST(str));
4786167eca2Schristos 	if (col != NULL && col->width < msz)
4796167eca2Schristos 		col->width = msz;
4806167eca2Schristos 	return msz;
481c0d9444aSjoerg }
482c0d9444aSjoerg 
4836167eca2Schristos static size_t
tblcalc_number(struct rofftbl * tbl,struct roffcol * col,const struct tbl_opts * opts,const struct tbl_dat * dp)484c0d9444aSjoerg tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
48570f041f9Sjoerg 		const struct tbl_opts *opts, const struct tbl_dat *dp)
486c0d9444aSjoerg {
4876167eca2Schristos 	const char	*cp, *lastdigit, *lastpoint;
4886167eca2Schristos 	size_t		 intsz, totsz;
489c0d9444aSjoerg 	char		 buf[2];
490c0d9444aSjoerg 
4916167eca2Schristos 	if (dp->string == NULL || *dp->string == '\0')
4926167eca2Schristos 		return 0;
4936167eca2Schristos 
4946167eca2Schristos 	totsz = (*tbl->slen)(dp->string, tbl->arg);
4956167eca2Schristos 	if (col == NULL)
4966167eca2Schristos 		return totsz;
4976167eca2Schristos 
498c0d9444aSjoerg 	/*
4996167eca2Schristos 	 * Find the last digit and
5006167eca2Schristos 	 * the last decimal point that is adjacent to a digit.
5016167eca2Schristos 	 * The alignment indicator "\&" overrides everything.
502c0d9444aSjoerg 	 */
503c0d9444aSjoerg 
5046167eca2Schristos 	lastdigit = lastpoint = NULL;
5056167eca2Schristos 	for (cp = dp->string; cp[0] != '\0'; cp++) {
5066167eca2Schristos 		if (cp[0] == '\\' && cp[1] == '&') {
5076167eca2Schristos 			lastdigit = lastpoint = cp;
5086167eca2Schristos 			break;
5096167eca2Schristos 		} else if (cp[0] == opts->decimal &&
5106167eca2Schristos 		    (isdigit((unsigned char)cp[1]) ||
5116167eca2Schristos 		     (cp > dp->string && isdigit((unsigned char)cp[-1]))))
5126167eca2Schristos 			lastpoint = cp;
5136167eca2Schristos 		else if (isdigit((unsigned char)cp[0]))
5146167eca2Schristos 			lastdigit = cp;
515c0d9444aSjoerg 	}
5166167eca2Schristos 
5176167eca2Schristos 	/* Not a number, treat as a literal string. */
5186167eca2Schristos 
5196167eca2Schristos 	if (lastdigit == NULL) {
5206167eca2Schristos 		if (col != NULL && col->width < totsz)
5216167eca2Schristos 			col->width = totsz;
5226167eca2Schristos 		return totsz;
5236167eca2Schristos 	}
5246167eca2Schristos 
5256167eca2Schristos 	/* Measure the width of the integer part. */
5266167eca2Schristos 
5276167eca2Schristos 	if (lastpoint == NULL)
5286167eca2Schristos 		lastpoint = lastdigit + 1;
5296167eca2Schristos 	intsz = 0;
5306167eca2Schristos 	buf[1] = '\0';
5316167eca2Schristos 	for (cp = dp->string; cp < lastpoint; cp++) {
5326167eca2Schristos 		buf[0] = cp[0];
5336167eca2Schristos 		intsz += (*tbl->slen)(buf, tbl->arg);
5346167eca2Schristos 	}
5356167eca2Schristos 
5366167eca2Schristos 	/*
5376167eca2Schristos          * If this number has more integer digits than all numbers
5386167eca2Schristos          * seen on earlier lines, shift them all to the right.
5396167eca2Schristos 	 * If it has fewer, shift this number to the right.
5406167eca2Schristos 	 */
5416167eca2Schristos 
5426167eca2Schristos 	if (intsz > col->decimal) {
5436167eca2Schristos 		col->nwidth += intsz - col->decimal;
5446167eca2Schristos 		col->decimal = intsz;
545c0d9444aSjoerg 	} else
5466167eca2Schristos 		totsz += col->decimal - intsz;
547c0d9444aSjoerg 
5486167eca2Schristos 	/* Update the maximum total width seen so far. */
549c0d9444aSjoerg 
5506167eca2Schristos 	if (totsz > col->nwidth)
5516167eca2Schristos 		col->nwidth = totsz;
5526167eca2Schristos 	return totsz;
553c0d9444aSjoerg }
554