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