1*e8a65004Sschwarze /* $OpenBSD: out.c,v 1.42 2017/06/27 18:23:29 schwarze Exp $ */ 24175bdabSschwarze /* 3ec04407bSschwarze * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4eb274914Sschwarze * Copyright (c) 2011, 2014, 2015, 2017 Ingo Schwarze <schwarze@openbsd.org> 54175bdabSschwarze * 64175bdabSschwarze * Permission to use, copy, modify, and distribute this software for any 74175bdabSschwarze * purpose with or without fee is hereby granted, provided that the above 84175bdabSschwarze * copyright notice and this permission notice appear in all copies. 94175bdabSschwarze * 104175bdabSschwarze * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 114175bdabSschwarze * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 124175bdabSschwarze * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 134175bdabSschwarze * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 144175bdabSschwarze * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 154175bdabSschwarze * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 164175bdabSschwarze * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 174175bdabSschwarze */ 184175bdabSschwarze #include <sys/types.h> 194175bdabSschwarze 20b822ca0dSschwarze #include <assert.h> 21*e8a65004Sschwarze #include <stdint.h> 224175bdabSschwarze #include <stdlib.h> 23b822ca0dSschwarze #include <string.h> 24b822ca0dSschwarze #include <time.h> 254175bdabSschwarze 264f4f7972Sschwarze #include "mandoc_aux.h" 27ec04407bSschwarze #include "mandoc.h" 284175bdabSschwarze #include "out.h" 294175bdabSschwarze 30ec04407bSschwarze static void tblcalc_data(struct rofftbl *, struct roffcol *, 312c3e66c4Sschwarze const struct tbl_opts *, const struct tbl_dat *, 322c3e66c4Sschwarze size_t); 33ec04407bSschwarze static void tblcalc_literal(struct rofftbl *, struct roffcol *, 342c3e66c4Sschwarze const struct tbl_dat *, size_t); 35ec04407bSschwarze static void tblcalc_number(struct rofftbl *, struct roffcol *, 36c7497e73Sschwarze const struct tbl_opts *, const struct tbl_dat *); 37ec04407bSschwarze 3849aff9f8Sschwarze 394175bdabSschwarze /* 40a09d548bSschwarze * Parse the *src string and store a scaling unit into *dst. 41a09d548bSschwarze * If the string doesn't specify the unit, use the default. 42a09d548bSschwarze * If no default is specified, fail. 43ecd22486Sschwarze * Return a pointer to the byte after the last byte used, 44ecd22486Sschwarze * or NULL on total failure. 454175bdabSschwarze */ 46ecd22486Sschwarze const char * 474175bdabSschwarze a2roffsu(const char *src, struct roffsu *dst, enum roffscale def) 484175bdabSschwarze { 49a09d548bSschwarze char *endptr; 504175bdabSschwarze 5184f230baSschwarze dst->unit = def == SCALE_MAX ? SCALE_BU : def; 5284f230baSschwarze dst->scale = strtod(src, &endptr); 5384f230baSschwarze if (endptr == src) 54ecd22486Sschwarze return NULL; 554175bdabSschwarze 5684f230baSschwarze switch (*endptr++) { 5749aff9f8Sschwarze case 'c': 5884f230baSschwarze dst->unit = SCALE_CM; 594175bdabSschwarze break; 6049aff9f8Sschwarze case 'i': 6184f230baSschwarze dst->unit = SCALE_IN; 624175bdabSschwarze break; 6349aff9f8Sschwarze case 'f': 6484f230baSschwarze dst->unit = SCALE_FS; 654175bdabSschwarze break; 6649aff9f8Sschwarze case 'M': 6784f230baSschwarze dst->unit = SCALE_MM; 6884f230baSschwarze break; 6984f230baSschwarze case 'm': 7084f230baSschwarze dst->unit = SCALE_EM; 714175bdabSschwarze break; 7249aff9f8Sschwarze case 'n': 7384f230baSschwarze dst->unit = SCALE_EN; 744175bdabSschwarze break; 7584f230baSschwarze case 'P': 7684f230baSschwarze dst->unit = SCALE_PC; 7784f230baSschwarze break; 7884f230baSschwarze case 'p': 7984f230baSschwarze dst->unit = SCALE_PT; 8084f230baSschwarze break; 8184f230baSschwarze case 'u': 8284f230baSschwarze dst->unit = SCALE_BU; 8384f230baSschwarze break; 8484f230baSschwarze case 'v': 8584f230baSschwarze dst->unit = SCALE_VS; 8684f230baSschwarze break; 874175bdabSschwarze default: 8823b9ae24Sschwarze endptr--; 8984f230baSschwarze if (SCALE_MAX == def) 90ecd22486Sschwarze return NULL; 9184f230baSschwarze dst->unit = def; 9284f230baSschwarze break; 934175bdabSschwarze } 94ecd22486Sschwarze return endptr; 954175bdabSschwarze } 96b822ca0dSschwarze 97ec04407bSschwarze /* 98ec04407bSschwarze * Calculate the abstract widths and decimal positions of columns in a 99ec04407bSschwarze * table. This routine allocates the columns structures then runs over 100ec04407bSschwarze * all rows and cells in the table. The function pointers in "tbl" are 101ec04407bSschwarze * used for the actual width calculations. 102ec04407bSschwarze */ 103ec04407bSschwarze void 10446fa2066Sschwarze tblcalc(struct rofftbl *tbl, const struct tbl_span *sp, 105d1c3ec49Sschwarze size_t offset, size_t rmargin) 106ec04407bSschwarze { 1072c3e66c4Sschwarze struct roffsu su; 10899d91ba7Sschwarze const struct tbl_opts *opts; 109ec04407bSschwarze const struct tbl_dat *dp; 110ec04407bSschwarze struct roffcol *col; 11146fa2066Sschwarze size_t ewidth, xwidth; 1120ebcd99eSschwarze int spans; 11399d91ba7Sschwarze int icol, maxcol, necol, nxcol, quirkcol; 114ec04407bSschwarze 115ec04407bSschwarze /* 116ec04407bSschwarze * Allocate the master column specifiers. These will hold the 117ec04407bSschwarze * widths and decimal positions for all cells in the column. It 118ec04407bSschwarze * must be freed and nullified by the caller. 119ec04407bSschwarze */ 120ec04407bSschwarze 121ec04407bSschwarze assert(NULL == tbl->cols); 12249aff9f8Sschwarze tbl->cols = mandoc_calloc((size_t)sp->opts->cols, 12349aff9f8Sschwarze sizeof(struct roffcol)); 12499d91ba7Sschwarze opts = sp->opts; 125ec04407bSschwarze 126b69c4eb3Sschwarze for (maxcol = -1; sp; sp = sp->next) { 127ec04407bSschwarze if (TBL_SPAN_DATA != sp->pos) 128ec04407bSschwarze continue; 1290ebcd99eSschwarze spans = 1; 130ec04407bSschwarze /* 131ec04407bSschwarze * Account for the data cells in the layout, matching it 132ec04407bSschwarze * to data cells in the data section. 133ec04407bSschwarze */ 134ec04407bSschwarze for (dp = sp->first; dp; dp = dp->next) { 1350ebcd99eSschwarze /* Do not used spanned cells in the calculation. */ 1360ebcd99eSschwarze if (0 < --spans) 1370ebcd99eSschwarze continue; 1380ebcd99eSschwarze spans = dp->spans; 1390ebcd99eSschwarze if (1 < spans) 1400ebcd99eSschwarze continue; 1415f6d1ba3Sschwarze icol = dp->layout->col; 142*e8a65004Sschwarze while (maxcol < icol) 143*e8a65004Sschwarze tbl->cols[++maxcol].spacing = SIZE_MAX; 14446fa2066Sschwarze col = tbl->cols + icol; 14546fa2066Sschwarze col->flags |= dp->layout->flags; 14646fa2066Sschwarze if (dp->layout->flags & TBL_CELL_WIGN) 14746fa2066Sschwarze continue; 1482c3e66c4Sschwarze if (dp->layout->wstr != NULL && 1492c3e66c4Sschwarze dp->layout->width == 0 && 1502c3e66c4Sschwarze a2roffsu(dp->layout->wstr, &su, SCALE_EN) 1512c3e66c4Sschwarze != NULL) 1522c3e66c4Sschwarze dp->layout->width = 1532c3e66c4Sschwarze (*tbl->sulen)(&su, tbl->arg); 1542c3e66c4Sschwarze if (col->width < dp->layout->width) 1552c3e66c4Sschwarze col->width = dp->layout->width; 156*e8a65004Sschwarze if (dp->layout->spacing != SIZE_MAX && 157*e8a65004Sschwarze (col->spacing == SIZE_MAX || 158*e8a65004Sschwarze col->spacing < dp->layout->spacing)) 159*e8a65004Sschwarze col->spacing = dp->layout->spacing; 160d1c3ec49Sschwarze tblcalc_data(tbl, col, opts, dp, 161238123d0Sschwarze dp->block == 0 ? 0 : 162238123d0Sschwarze dp->layout->width ? dp->layout->width : 163e6d4e954Sschwarze rmargin ? (rmargin + sp->opts->cols / 2) 164e6d4e954Sschwarze / (sp->opts->cols + 1) : 0); 165ec04407bSschwarze } 166ec04407bSschwarze } 16746fa2066Sschwarze 16846fa2066Sschwarze /* 16946fa2066Sschwarze * Count columns to equalize and columns to maximize. 17046fa2066Sschwarze * Find maximum width of the columns to equalize. 17146fa2066Sschwarze * Find total width of the columns *not* to maximize. 17246fa2066Sschwarze */ 17346fa2066Sschwarze 17446fa2066Sschwarze necol = nxcol = 0; 17546fa2066Sschwarze ewidth = xwidth = 0; 17646fa2066Sschwarze for (icol = 0; icol <= maxcol; icol++) { 17746fa2066Sschwarze col = tbl->cols + icol; 178*e8a65004Sschwarze if (col->spacing == SIZE_MAX || icol == maxcol) 179*e8a65004Sschwarze col->spacing = 3; 18046fa2066Sschwarze if (col->flags & TBL_CELL_EQUAL) { 18146fa2066Sschwarze necol++; 18246fa2066Sschwarze if (ewidth < col->width) 18346fa2066Sschwarze ewidth = col->width; 18446fa2066Sschwarze } 18546fa2066Sschwarze if (col->flags & TBL_CELL_WMAX) 18646fa2066Sschwarze nxcol++; 18746fa2066Sschwarze else 18846fa2066Sschwarze xwidth += col->width; 18946fa2066Sschwarze } 19046fa2066Sschwarze 19146fa2066Sschwarze /* 19246fa2066Sschwarze * Equalize columns, if requested for any of them. 19346fa2066Sschwarze * Update total width of the columns not to maximize. 19446fa2066Sschwarze */ 19546fa2066Sschwarze 19646fa2066Sschwarze if (necol) { 19746fa2066Sschwarze for (icol = 0; icol <= maxcol; icol++) { 19846fa2066Sschwarze col = tbl->cols + icol; 19946fa2066Sschwarze if ( ! (col->flags & TBL_CELL_EQUAL)) 20046fa2066Sschwarze continue; 20146fa2066Sschwarze if (col->width == ewidth) 20246fa2066Sschwarze continue; 203d1c3ec49Sschwarze if (nxcol && rmargin) 20446fa2066Sschwarze xwidth += ewidth - col->width; 20546fa2066Sschwarze col->width = ewidth; 20646fa2066Sschwarze } 20746fa2066Sschwarze } 20846fa2066Sschwarze 20946fa2066Sschwarze /* 21046fa2066Sschwarze * If there are any columns to maximize, find the total 21146fa2066Sschwarze * available width, deducting 3n margins between columns. 21246fa2066Sschwarze * Distribute the available width evenly. 21346fa2066Sschwarze */ 21446fa2066Sschwarze 215d1c3ec49Sschwarze if (nxcol && rmargin) { 216eb274914Sschwarze xwidth += 3*maxcol + 21799d91ba7Sschwarze (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ? 21899d91ba7Sschwarze 2 : !!opts->lvert + !!opts->rvert); 219d1c3ec49Sschwarze if (rmargin <= offset + xwidth) 220eb274914Sschwarze return; 221d1c3ec49Sschwarze xwidth = rmargin - offset - xwidth; 22299d91ba7Sschwarze 22399d91ba7Sschwarze /* 22499d91ba7Sschwarze * Emulate a bug in GNU tbl width calculation that 22599d91ba7Sschwarze * manifests itself for large numbers of x-columns. 22699d91ba7Sschwarze * Emulating it for 5 x-columns gives identical 22799d91ba7Sschwarze * behaviour for up to 6 x-columns. 22899d91ba7Sschwarze */ 22999d91ba7Sschwarze 23099d91ba7Sschwarze if (nxcol == 5) { 23199d91ba7Sschwarze quirkcol = xwidth % nxcol + 2; 23299d91ba7Sschwarze if (quirkcol != 3 && quirkcol != 4) 23399d91ba7Sschwarze quirkcol = -1; 23499d91ba7Sschwarze } else 23599d91ba7Sschwarze quirkcol = -1; 23699d91ba7Sschwarze 23799d91ba7Sschwarze necol = 0; 23899d91ba7Sschwarze ewidth = 0; 23946fa2066Sschwarze for (icol = 0; icol <= maxcol; icol++) { 24046fa2066Sschwarze col = tbl->cols + icol; 24146fa2066Sschwarze if ( ! (col->flags & TBL_CELL_WMAX)) 24246fa2066Sschwarze continue; 24399d91ba7Sschwarze col->width = (double)xwidth * ++necol / nxcol 24499d91ba7Sschwarze - ewidth + 0.4995; 24599d91ba7Sschwarze if (necol == quirkcol) 24699d91ba7Sschwarze col->width--; 24799d91ba7Sschwarze ewidth += col->width; 24846fa2066Sschwarze } 24946fa2066Sschwarze } 250ec04407bSschwarze } 251ec04407bSschwarze 252ec04407bSschwarze static void 253ec04407bSschwarze tblcalc_data(struct rofftbl *tbl, struct roffcol *col, 2542c3e66c4Sschwarze const struct tbl_opts *opts, const struct tbl_dat *dp, size_t mw) 255ec04407bSschwarze { 256ec04407bSschwarze size_t sz; 257ec04407bSschwarze 258ec04407bSschwarze /* Branch down into data sub-types. */ 259ec04407bSschwarze 260ec04407bSschwarze switch (dp->layout->pos) { 26149aff9f8Sschwarze case TBL_CELL_HORIZ: 26249aff9f8Sschwarze case TBL_CELL_DHORIZ: 263ec04407bSschwarze sz = (*tbl->len)(1, tbl->arg); 264ec04407bSschwarze if (col->width < sz) 265ec04407bSschwarze col->width = sz; 266ec04407bSschwarze break; 26749aff9f8Sschwarze case TBL_CELL_LONG: 26849aff9f8Sschwarze case TBL_CELL_CENTRE: 26949aff9f8Sschwarze case TBL_CELL_LEFT: 27049aff9f8Sschwarze case TBL_CELL_RIGHT: 2712c3e66c4Sschwarze tblcalc_literal(tbl, col, dp, mw); 272ec04407bSschwarze break; 27349aff9f8Sschwarze case TBL_CELL_NUMBER: 274c7497e73Sschwarze tblcalc_number(tbl, col, opts, dp); 275ec04407bSschwarze break; 27649aff9f8Sschwarze case TBL_CELL_DOWN: 2778351ebcfSschwarze break; 278ec04407bSschwarze default: 279ec04407bSschwarze abort(); 280ec04407bSschwarze } 281ec04407bSschwarze } 282ec04407bSschwarze 283ec04407bSschwarze static void 284ec04407bSschwarze tblcalc_literal(struct rofftbl *tbl, struct roffcol *col, 2852c3e66c4Sschwarze const struct tbl_dat *dp, size_t mw) 286ec04407bSschwarze { 2872c3e66c4Sschwarze const char *str; /* Beginning of the first line. */ 2882c3e66c4Sschwarze const char *beg; /* Beginning of the current line. */ 2892c3e66c4Sschwarze char *end; /* End of the current line. */ 290d1c3ec49Sschwarze size_t lsz; /* Length of the current line. */ 291d1c3ec49Sschwarze size_t wsz; /* Length of the current word. */ 292ec04407bSschwarze 2932c3e66c4Sschwarze if (dp->string == NULL || *dp->string == '\0') 2942c3e66c4Sschwarze return; 2952c3e66c4Sschwarze str = mw ? mandoc_strdup(dp->string) : dp->string; 296d1c3ec49Sschwarze lsz = 0; 2972c3e66c4Sschwarze for (beg = str; beg != NULL && *beg != '\0'; beg = end) { 2982c3e66c4Sschwarze end = mw ? strchr(beg, ' ') : NULL; 2992c3e66c4Sschwarze if (end != NULL) { 3002c3e66c4Sschwarze *end++ = '\0'; 3012c3e66c4Sschwarze while (*end == ' ') 3022c3e66c4Sschwarze end++; 3032c3e66c4Sschwarze } 304d1c3ec49Sschwarze wsz = (*tbl->slen)(beg, tbl->arg); 305d1c3ec49Sschwarze if (mw && lsz && lsz + 1 + wsz <= mw) 306d1c3ec49Sschwarze lsz += 1 + wsz; 307d1c3ec49Sschwarze else 308d1c3ec49Sschwarze lsz = wsz; 309d1c3ec49Sschwarze if (col->width < lsz) 310d1c3ec49Sschwarze col->width = lsz; 311ec04407bSschwarze } 3122c3e66c4Sschwarze if (mw) 3132c3e66c4Sschwarze free((void *)str); 3142c3e66c4Sschwarze } 315ec04407bSschwarze 316ec04407bSschwarze static void 317ec04407bSschwarze tblcalc_number(struct rofftbl *tbl, struct roffcol *col, 318c7497e73Sschwarze const struct tbl_opts *opts, const struct tbl_dat *dp) 319ec04407bSschwarze { 320ec04407bSschwarze int i; 3218351ebcfSschwarze size_t sz, psz, ssz, d; 322ec04407bSschwarze const char *str; 3238351ebcfSschwarze char *cp; 324ec04407bSschwarze char buf[2]; 325ec04407bSschwarze 326ec04407bSschwarze /* 327ec04407bSschwarze * First calculate number width and decimal place (last + 1 for 3280ebcd99eSschwarze * non-decimal numbers). If the stored decimal is subsequent to 329ec04407bSschwarze * ours, make our size longer by that difference 330ec04407bSschwarze * (right-"shifting"); similarly, if ours is subsequent the 331ec04407bSschwarze * stored, then extend the stored size by the difference. 332ec04407bSschwarze * Finally, re-assign the stored values. 333ec04407bSschwarze */ 334ec04407bSschwarze 3358351ebcfSschwarze str = dp->string ? dp->string : ""; 336ec04407bSschwarze sz = (*tbl->slen)(str, tbl->arg); 337ec04407bSschwarze 3388351ebcfSschwarze /* FIXME: TBL_DATA_HORIZ et al.? */ 3398351ebcfSschwarze 340c7497e73Sschwarze buf[0] = opts->decimal; 341ec04407bSschwarze buf[1] = '\0'; 342ec04407bSschwarze 343ec04407bSschwarze psz = (*tbl->slen)(buf, tbl->arg); 344ec04407bSschwarze 345c7497e73Sschwarze if (NULL != (cp = strrchr(str, opts->decimal))) { 346ec04407bSschwarze buf[1] = '\0'; 347ec04407bSschwarze for (ssz = 0, i = 0; cp != &str[i]; i++) { 348ec04407bSschwarze buf[0] = str[i]; 349ec04407bSschwarze ssz += (*tbl->slen)(buf, tbl->arg); 350ec04407bSschwarze } 351ec04407bSschwarze d = ssz + psz; 352ec04407bSschwarze } else 353ec04407bSschwarze d = sz + psz; 354ec04407bSschwarze 355ec04407bSschwarze /* Adjust the settings for this column. */ 356ec04407bSschwarze 357ec04407bSschwarze if (col->decimal > d) { 358ec04407bSschwarze sz += col->decimal - d; 359ec04407bSschwarze d = col->decimal; 360ec04407bSschwarze } else 361ec04407bSschwarze col->width += d - col->decimal; 362ec04407bSschwarze 363ec04407bSschwarze if (sz > col->width) 364ec04407bSschwarze col->width = sz; 365ec04407bSschwarze if (d > col->decimal) 366ec04407bSschwarze col->decimal = d; 367ec04407bSschwarze } 368