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 * 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 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 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 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 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