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