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