1 /* $OpenBSD: out.c,v 1.42 2017/06/27 18:23:29 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2011, 2014, 2015, 2017 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 <stdint.h> 22 #include <stdlib.h> 23 #include <string.h> 24 #include <time.h> 25 26 #include "mandoc_aux.h" 27 #include "mandoc.h" 28 #include "out.h" 29 30 static void tblcalc_data(struct rofftbl *, struct roffcol *, 31 const struct tbl_opts *, const struct tbl_dat *, 32 size_t); 33 static void tblcalc_literal(struct rofftbl *, struct roffcol *, 34 const struct tbl_dat *, size_t); 35 static void tblcalc_number(struct rofftbl *, struct roffcol *, 36 const struct tbl_opts *, const struct tbl_dat *); 37 38 39 /* 40 * Parse the *src string and store a scaling unit into *dst. 41 * If the string doesn't specify the unit, use the default. 42 * If no default is specified, fail. 43 * Return a pointer to the byte after the last byte used, 44 * or NULL on total failure. 45 */ 46 const char * 47 a2roffsu(const char *src, struct roffsu *dst, enum roffscale def) 48 { 49 char *endptr; 50 51 dst->unit = def == SCALE_MAX ? SCALE_BU : def; 52 dst->scale = strtod(src, &endptr); 53 if (endptr == src) 54 return NULL; 55 56 switch (*endptr++) { 57 case 'c': 58 dst->unit = SCALE_CM; 59 break; 60 case 'i': 61 dst->unit = SCALE_IN; 62 break; 63 case 'f': 64 dst->unit = SCALE_FS; 65 break; 66 case 'M': 67 dst->unit = SCALE_MM; 68 break; 69 case 'm': 70 dst->unit = SCALE_EM; 71 break; 72 case 'n': 73 dst->unit = SCALE_EN; 74 break; 75 case 'P': 76 dst->unit = SCALE_PC; 77 break; 78 case 'p': 79 dst->unit = SCALE_PT; 80 break; 81 case 'u': 82 dst->unit = SCALE_BU; 83 break; 84 case 'v': 85 dst->unit = SCALE_VS; 86 break; 87 default: 88 endptr--; 89 if (SCALE_MAX == def) 90 return NULL; 91 dst->unit = def; 92 break; 93 } 94 return endptr; 95 } 96 97 /* 98 * Calculate the abstract widths and decimal positions of columns in a 99 * table. This routine allocates the columns structures then runs over 100 * all rows and cells in the table. The function pointers in "tbl" are 101 * used for the actual width calculations. 102 */ 103 void 104 tblcalc(struct rofftbl *tbl, const struct tbl_span *sp, 105 size_t offset, size_t rmargin) 106 { 107 struct roffsu su; 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 while (maxcol < icol) 143 tbl->cols[++maxcol].spacing = SIZE_MAX; 144 col = tbl->cols + icol; 145 col->flags |= dp->layout->flags; 146 if (dp->layout->flags & TBL_CELL_WIGN) 147 continue; 148 if (dp->layout->wstr != NULL && 149 dp->layout->width == 0 && 150 a2roffsu(dp->layout->wstr, &su, SCALE_EN) 151 != NULL) 152 dp->layout->width = 153 (*tbl->sulen)(&su, tbl->arg); 154 if (col->width < dp->layout->width) 155 col->width = dp->layout->width; 156 if (dp->layout->spacing != SIZE_MAX && 157 (col->spacing == SIZE_MAX || 158 col->spacing < dp->layout->spacing)) 159 col->spacing = dp->layout->spacing; 160 tblcalc_data(tbl, col, opts, dp, 161 dp->block == 0 ? 0 : 162 dp->layout->width ? dp->layout->width : 163 rmargin ? (rmargin + sp->opts->cols / 2) 164 / (sp->opts->cols + 1) : 0); 165 } 166 } 167 168 /* 169 * Count columns to equalize and columns to maximize. 170 * Find maximum width of the columns to equalize. 171 * Find total width of the columns *not* to maximize. 172 */ 173 174 necol = nxcol = 0; 175 ewidth = xwidth = 0; 176 for (icol = 0; icol <= maxcol; icol++) { 177 col = tbl->cols + icol; 178 if (col->spacing == SIZE_MAX || icol == maxcol) 179 col->spacing = 3; 180 if (col->flags & TBL_CELL_EQUAL) { 181 necol++; 182 if (ewidth < col->width) 183 ewidth = col->width; 184 } 185 if (col->flags & TBL_CELL_WMAX) 186 nxcol++; 187 else 188 xwidth += col->width; 189 } 190 191 /* 192 * Equalize columns, if requested for any of them. 193 * Update total width of the columns not to maximize. 194 */ 195 196 if (necol) { 197 for (icol = 0; icol <= maxcol; icol++) { 198 col = tbl->cols + icol; 199 if ( ! (col->flags & TBL_CELL_EQUAL)) 200 continue; 201 if (col->width == ewidth) 202 continue; 203 if (nxcol && rmargin) 204 xwidth += ewidth - col->width; 205 col->width = ewidth; 206 } 207 } 208 209 /* 210 * If there are any columns to maximize, find the total 211 * available width, deducting 3n margins between columns. 212 * Distribute the available width evenly. 213 */ 214 215 if (nxcol && rmargin) { 216 xwidth += 3*maxcol + 217 (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ? 218 2 : !!opts->lvert + !!opts->rvert); 219 if (rmargin <= offset + xwidth) 220 return; 221 xwidth = rmargin - offset - xwidth; 222 223 /* 224 * Emulate a bug in GNU tbl width calculation that 225 * manifests itself for large numbers of x-columns. 226 * Emulating it for 5 x-columns gives identical 227 * behaviour for up to 6 x-columns. 228 */ 229 230 if (nxcol == 5) { 231 quirkcol = xwidth % nxcol + 2; 232 if (quirkcol != 3 && quirkcol != 4) 233 quirkcol = -1; 234 } else 235 quirkcol = -1; 236 237 necol = 0; 238 ewidth = 0; 239 for (icol = 0; icol <= maxcol; icol++) { 240 col = tbl->cols + icol; 241 if ( ! (col->flags & TBL_CELL_WMAX)) 242 continue; 243 col->width = (double)xwidth * ++necol / nxcol 244 - ewidth + 0.4995; 245 if (necol == quirkcol) 246 col->width--; 247 ewidth += col->width; 248 } 249 } 250 } 251 252 static void 253 tblcalc_data(struct rofftbl *tbl, struct roffcol *col, 254 const struct tbl_opts *opts, const struct tbl_dat *dp, size_t mw) 255 { 256 size_t sz; 257 258 /* Branch down into data sub-types. */ 259 260 switch (dp->layout->pos) { 261 case TBL_CELL_HORIZ: 262 case TBL_CELL_DHORIZ: 263 sz = (*tbl->len)(1, tbl->arg); 264 if (col->width < sz) 265 col->width = sz; 266 break; 267 case TBL_CELL_LONG: 268 case TBL_CELL_CENTRE: 269 case TBL_CELL_LEFT: 270 case TBL_CELL_RIGHT: 271 tblcalc_literal(tbl, col, dp, mw); 272 break; 273 case TBL_CELL_NUMBER: 274 tblcalc_number(tbl, col, opts, dp); 275 break; 276 case TBL_CELL_DOWN: 277 break; 278 default: 279 abort(); 280 } 281 } 282 283 static void 284 tblcalc_literal(struct rofftbl *tbl, struct roffcol *col, 285 const struct tbl_dat *dp, size_t mw) 286 { 287 const char *str; /* Beginning of the first line. */ 288 const char *beg; /* Beginning of the current line. */ 289 char *end; /* End of the current line. */ 290 size_t lsz; /* Length of the current line. */ 291 size_t wsz; /* Length of the current word. */ 292 293 if (dp->string == NULL || *dp->string == '\0') 294 return; 295 str = mw ? mandoc_strdup(dp->string) : dp->string; 296 lsz = 0; 297 for (beg = str; beg != NULL && *beg != '\0'; beg = end) { 298 end = mw ? strchr(beg, ' ') : NULL; 299 if (end != NULL) { 300 *end++ = '\0'; 301 while (*end == ' ') 302 end++; 303 } 304 wsz = (*tbl->slen)(beg, tbl->arg); 305 if (mw && lsz && lsz + 1 + wsz <= mw) 306 lsz += 1 + wsz; 307 else 308 lsz = wsz; 309 if (col->width < lsz) 310 col->width = lsz; 311 } 312 if (mw) 313 free((void *)str); 314 } 315 316 static void 317 tblcalc_number(struct rofftbl *tbl, struct roffcol *col, 318 const struct tbl_opts *opts, const struct tbl_dat *dp) 319 { 320 int i; 321 size_t sz, psz, ssz, d; 322 const char *str; 323 char *cp; 324 char buf[2]; 325 326 /* 327 * First calculate number width and decimal place (last + 1 for 328 * non-decimal numbers). If the stored decimal is subsequent to 329 * ours, make our size longer by that difference 330 * (right-"shifting"); similarly, if ours is subsequent the 331 * stored, then extend the stored size by the difference. 332 * Finally, re-assign the stored values. 333 */ 334 335 str = dp->string ? dp->string : ""; 336 sz = (*tbl->slen)(str, tbl->arg); 337 338 /* FIXME: TBL_DATA_HORIZ et al.? */ 339 340 buf[0] = opts->decimal; 341 buf[1] = '\0'; 342 343 psz = (*tbl->slen)(buf, tbl->arg); 344 345 if (NULL != (cp = strrchr(str, opts->decimal))) { 346 buf[1] = '\0'; 347 for (ssz = 0, i = 0; cp != &str[i]; i++) { 348 buf[0] = str[i]; 349 ssz += (*tbl->slen)(buf, tbl->arg); 350 } 351 d = ssz + psz; 352 } else 353 d = sz + psz; 354 355 /* Adjust the settings for this column. */ 356 357 if (col->decimal > d) { 358 sz += col->decimal - d; 359 d = col->decimal; 360 } else 361 col->width += d - col->decimal; 362 363 if (sz > col->width) 364 col->width = sz; 365 if (d > col->decimal) 366 col->decimal = d; 367 } 368