1 /* $OpenBSD: out.c,v 1.34 2015/10/12 00:07:27 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2011, 2014, 2015 Ingo Schwarze <schwarze@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 #include <sys/types.h> 19 20 #include <assert.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <time.h> 24 25 #include "mandoc_aux.h" 26 #include "mandoc.h" 27 #include "out.h" 28 29 static void tblcalc_data(struct rofftbl *, struct roffcol *, 30 const struct tbl_opts *, const struct tbl_dat *); 31 static void tblcalc_literal(struct rofftbl *, struct roffcol *, 32 const struct tbl_dat *); 33 static void tblcalc_number(struct rofftbl *, struct roffcol *, 34 const struct tbl_opts *, const struct tbl_dat *); 35 36 37 /* 38 * Parse the *src string and store a scaling unit into *dst. 39 * If the string doesn't specify the unit, use the default. 40 * If no default is specified, fail. 41 * Return 2 on complete success, 1 when a conversion was done, 42 * but there was trailing garbage, and 0 on total failure. 43 */ 44 int 45 a2roffsu(const char *src, struct roffsu *dst, enum roffscale def) 46 { 47 char *endptr; 48 49 dst->unit = def == SCALE_MAX ? SCALE_BU : def; 50 dst->scale = strtod(src, &endptr); 51 if (endptr == src) 52 return 0; 53 54 switch (*endptr++) { 55 case 'c': 56 dst->unit = SCALE_CM; 57 break; 58 case 'i': 59 dst->unit = SCALE_IN; 60 break; 61 case 'f': 62 dst->unit = SCALE_FS; 63 break; 64 case 'M': 65 dst->unit = SCALE_MM; 66 break; 67 case 'm': 68 dst->unit = SCALE_EM; 69 break; 70 case 'n': 71 dst->unit = SCALE_EN; 72 break; 73 case 'P': 74 dst->unit = SCALE_PC; 75 break; 76 case 'p': 77 dst->unit = SCALE_PT; 78 break; 79 case 'u': 80 dst->unit = SCALE_BU; 81 break; 82 case 'v': 83 dst->unit = SCALE_VS; 84 break; 85 case '\0': 86 endptr--; 87 /* FALLTHROUGH */ 88 default: 89 if (SCALE_MAX == def) 90 return 0; 91 dst->unit = def; 92 break; 93 } 94 95 return *endptr == '\0' ? 2 : 1; 96 } 97 98 /* 99 * Calculate the abstract widths and decimal positions of columns in a 100 * table. This routine allocates the columns structures then runs over 101 * all rows and cells in the table. The function pointers in "tbl" are 102 * used for the actual width calculations. 103 */ 104 void 105 tblcalc(struct rofftbl *tbl, const struct tbl_span *sp, 106 size_t totalwidth) 107 { 108 const struct tbl_opts *opts; 109 const struct tbl_dat *dp; 110 struct roffcol *col; 111 size_t ewidth, xwidth; 112 int spans; 113 int icol, maxcol, necol, nxcol, quirkcol; 114 115 /* 116 * Allocate the master column specifiers. These will hold the 117 * widths and decimal positions for all cells in the column. It 118 * must be freed and nullified by the caller. 119 */ 120 121 assert(NULL == tbl->cols); 122 tbl->cols = mandoc_calloc((size_t)sp->opts->cols, 123 sizeof(struct roffcol)); 124 opts = sp->opts; 125 126 for (maxcol = -1; sp; sp = sp->next) { 127 if (TBL_SPAN_DATA != sp->pos) 128 continue; 129 spans = 1; 130 /* 131 * Account for the data cells in the layout, matching it 132 * to data cells in the data section. 133 */ 134 for (dp = sp->first; dp; dp = dp->next) { 135 /* Do not used spanned cells in the calculation. */ 136 if (0 < --spans) 137 continue; 138 spans = dp->spans; 139 if (1 < spans) 140 continue; 141 icol = dp->layout->col; 142 if (maxcol < icol) 143 maxcol = icol; 144 col = tbl->cols + icol; 145 col->flags |= dp->layout->flags; 146 if (dp->layout->flags & TBL_CELL_WIGN) 147 continue; 148 tblcalc_data(tbl, col, opts, dp); 149 } 150 } 151 152 /* 153 * Count columns to equalize and columns to maximize. 154 * Find maximum width of the columns to equalize. 155 * Find total width of the columns *not* to maximize. 156 */ 157 158 necol = nxcol = 0; 159 ewidth = xwidth = 0; 160 for (icol = 0; icol <= maxcol; icol++) { 161 col = tbl->cols + icol; 162 if (col->flags & TBL_CELL_EQUAL) { 163 necol++; 164 if (ewidth < col->width) 165 ewidth = col->width; 166 } 167 if (col->flags & TBL_CELL_WMAX) 168 nxcol++; 169 else 170 xwidth += col->width; 171 } 172 173 /* 174 * Equalize columns, if requested for any of them. 175 * Update total width of the columns not to maximize. 176 */ 177 178 if (necol) { 179 for (icol = 0; icol <= maxcol; icol++) { 180 col = tbl->cols + icol; 181 if ( ! (col->flags & TBL_CELL_EQUAL)) 182 continue; 183 if (col->width == ewidth) 184 continue; 185 if (nxcol && totalwidth) 186 xwidth += ewidth - col->width; 187 col->width = ewidth; 188 } 189 } 190 191 /* 192 * If there are any columns to maximize, find the total 193 * available width, deducting 3n margins between columns. 194 * Distribute the available width evenly. 195 */ 196 197 if (nxcol && totalwidth) { 198 xwidth = totalwidth - xwidth - 3*maxcol - 199 (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ? 200 2 : !!opts->lvert + !!opts->rvert); 201 202 /* 203 * Emulate a bug in GNU tbl width calculation that 204 * manifests itself for large numbers of x-columns. 205 * Emulating it for 5 x-columns gives identical 206 * behaviour for up to 6 x-columns. 207 */ 208 209 if (nxcol == 5) { 210 quirkcol = xwidth % nxcol + 2; 211 if (quirkcol != 3 && quirkcol != 4) 212 quirkcol = -1; 213 } else 214 quirkcol = -1; 215 216 necol = 0; 217 ewidth = 0; 218 for (icol = 0; icol <= maxcol; icol++) { 219 col = tbl->cols + icol; 220 if ( ! (col->flags & TBL_CELL_WMAX)) 221 continue; 222 col->width = (double)xwidth * ++necol / nxcol 223 - ewidth + 0.4995; 224 if (necol == quirkcol) 225 col->width--; 226 ewidth += col->width; 227 } 228 } 229 } 230 231 static void 232 tblcalc_data(struct rofftbl *tbl, struct roffcol *col, 233 const struct tbl_opts *opts, const struct tbl_dat *dp) 234 { 235 size_t sz; 236 237 /* Branch down into data sub-types. */ 238 239 switch (dp->layout->pos) { 240 case TBL_CELL_HORIZ: 241 case TBL_CELL_DHORIZ: 242 sz = (*tbl->len)(1, tbl->arg); 243 if (col->width < sz) 244 col->width = sz; 245 break; 246 case TBL_CELL_LONG: 247 case TBL_CELL_CENTRE: 248 case TBL_CELL_LEFT: 249 case TBL_CELL_RIGHT: 250 tblcalc_literal(tbl, col, dp); 251 break; 252 case TBL_CELL_NUMBER: 253 tblcalc_number(tbl, col, opts, dp); 254 break; 255 case TBL_CELL_DOWN: 256 break; 257 default: 258 abort(); 259 } 260 } 261 262 static void 263 tblcalc_literal(struct rofftbl *tbl, struct roffcol *col, 264 const struct tbl_dat *dp) 265 { 266 size_t sz; 267 const char *str; 268 269 str = dp->string ? dp->string : ""; 270 sz = (*tbl->slen)(str, tbl->arg); 271 272 if (col->width < sz) 273 col->width = sz; 274 } 275 276 static void 277 tblcalc_number(struct rofftbl *tbl, struct roffcol *col, 278 const struct tbl_opts *opts, const struct tbl_dat *dp) 279 { 280 int i; 281 size_t sz, psz, ssz, d; 282 const char *str; 283 char *cp; 284 char buf[2]; 285 286 /* 287 * First calculate number width and decimal place (last + 1 for 288 * non-decimal numbers). If the stored decimal is subsequent to 289 * ours, make our size longer by that difference 290 * (right-"shifting"); similarly, if ours is subsequent the 291 * stored, then extend the stored size by the difference. 292 * Finally, re-assign the stored values. 293 */ 294 295 str = dp->string ? dp->string : ""; 296 sz = (*tbl->slen)(str, tbl->arg); 297 298 /* FIXME: TBL_DATA_HORIZ et al.? */ 299 300 buf[0] = opts->decimal; 301 buf[1] = '\0'; 302 303 psz = (*tbl->slen)(buf, tbl->arg); 304 305 if (NULL != (cp = strrchr(str, opts->decimal))) { 306 buf[1] = '\0'; 307 for (ssz = 0, i = 0; cp != &str[i]; i++) { 308 buf[0] = str[i]; 309 ssz += (*tbl->slen)(buf, tbl->arg); 310 } 311 d = ssz + psz; 312 } else 313 d = sz + psz; 314 315 /* Adjust the settings for this column. */ 316 317 if (col->decimal > d) { 318 sz += col->decimal - d; 319 d = col->decimal; 320 } else 321 col->width += d - col->decimal; 322 323 if (sz > col->width) 324 col->width = sz; 325 if (d > col->decimal) 326 col->decimal = d; 327 } 328