1 /* $OpenBSD: out.c,v 1.45 2018/08/19 23:10:16 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2011,2014,2015,2017,2018 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 <ctype.h> 22 #include <stdint.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <time.h> 26 27 #include "mandoc_aux.h" 28 #include "mandoc.h" 29 #include "out.h" 30 31 static void tblcalc_data(struct rofftbl *, struct roffcol *, 32 const struct tbl_opts *, const struct tbl_dat *, 33 size_t); 34 static void tblcalc_literal(struct rofftbl *, struct roffcol *, 35 const struct tbl_dat *, size_t); 36 static void tblcalc_number(struct rofftbl *, struct roffcol *, 37 const struct tbl_opts *, const struct tbl_dat *); 38 39 40 /* 41 * Parse the *src string and store a scaling unit into *dst. 42 * If the string doesn't specify the unit, use the default. 43 * If no default is specified, fail. 44 * Return a pointer to the byte after the last byte used, 45 * or NULL on total failure. 46 */ 47 const char * 48 a2roffsu(const char *src, struct roffsu *dst, enum roffscale def) 49 { 50 char *endptr; 51 52 dst->unit = def == SCALE_MAX ? SCALE_BU : def; 53 dst->scale = strtod(src, &endptr); 54 if (endptr == src) 55 return NULL; 56 57 switch (*endptr++) { 58 case 'c': 59 dst->unit = SCALE_CM; 60 break; 61 case 'i': 62 dst->unit = SCALE_IN; 63 break; 64 case 'f': 65 dst->unit = SCALE_FS; 66 break; 67 case 'M': 68 dst->unit = SCALE_MM; 69 break; 70 case 'm': 71 dst->unit = SCALE_EM; 72 break; 73 case 'n': 74 dst->unit = SCALE_EN; 75 break; 76 case 'P': 77 dst->unit = SCALE_PC; 78 break; 79 case 'p': 80 dst->unit = SCALE_PT; 81 break; 82 case 'u': 83 dst->unit = SCALE_BU; 84 break; 85 case 'v': 86 dst->unit = SCALE_VS; 87 break; 88 default: 89 endptr--; 90 if (SCALE_MAX == def) 91 return NULL; 92 dst->unit = def; 93 break; 94 } 95 return endptr; 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 offset, size_t rmargin) 107 { 108 struct roffsu su; 109 const struct tbl_opts *opts; 110 const struct tbl_dat *dp; 111 struct roffcol *col; 112 size_t ewidth, xwidth; 113 int spans; 114 int icol, maxcol, necol, nxcol, quirkcol; 115 116 /* 117 * Allocate the master column specifiers. These will hold the 118 * widths and decimal positions for all cells in the column. It 119 * must be freed and nullified by the caller. 120 */ 121 122 assert(NULL == tbl->cols); 123 tbl->cols = mandoc_calloc((size_t)sp->opts->cols, 124 sizeof(struct roffcol)); 125 opts = sp->opts; 126 127 for (maxcol = -1; sp; sp = sp->next) { 128 if (TBL_SPAN_DATA != sp->pos) 129 continue; 130 spans = 1; 131 /* 132 * Account for the data cells in the layout, matching it 133 * to data cells in the data section. 134 */ 135 for (dp = sp->first; dp; dp = dp->next) { 136 /* Do not used spanned cells in the calculation. */ 137 if (0 < --spans) 138 continue; 139 spans = dp->spans; 140 if (1 < spans) 141 continue; 142 icol = dp->layout->col; 143 while (maxcol < icol) 144 tbl->cols[++maxcol].spacing = SIZE_MAX; 145 col = tbl->cols + icol; 146 col->flags |= dp->layout->flags; 147 if (dp->layout->flags & TBL_CELL_WIGN) 148 continue; 149 if (dp->layout->wstr != NULL && 150 dp->layout->width == 0 && 151 a2roffsu(dp->layout->wstr, &su, SCALE_EN) 152 != NULL) 153 dp->layout->width = 154 (*tbl->sulen)(&su, tbl->arg); 155 if (col->width < dp->layout->width) 156 col->width = dp->layout->width; 157 if (dp->layout->spacing != SIZE_MAX && 158 (col->spacing == SIZE_MAX || 159 col->spacing < dp->layout->spacing)) 160 col->spacing = dp->layout->spacing; 161 tblcalc_data(tbl, col, opts, dp, 162 dp->block == 0 ? 0 : 163 dp->layout->width ? dp->layout->width : 164 rmargin ? (rmargin + sp->opts->cols / 2) 165 / (sp->opts->cols + 1) : 0); 166 } 167 } 168 169 /* 170 * Align numbers with text. 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->width > col->nwidth) 181 col->decimal += (col->width - col->nwidth) / 2; 182 else 183 col->width = col->nwidth; 184 if (col->spacing == SIZE_MAX || icol == maxcol) 185 col->spacing = 3; 186 if (col->flags & TBL_CELL_EQUAL) { 187 necol++; 188 if (ewidth < col->width) 189 ewidth = col->width; 190 } 191 if (col->flags & TBL_CELL_WMAX) 192 nxcol++; 193 else 194 xwidth += col->width; 195 } 196 197 /* 198 * Equalize columns, if requested for any of them. 199 * Update total width of the columns not to maximize. 200 */ 201 202 if (necol) { 203 for (icol = 0; icol <= maxcol; icol++) { 204 col = tbl->cols + icol; 205 if ( ! (col->flags & TBL_CELL_EQUAL)) 206 continue; 207 if (col->width == ewidth) 208 continue; 209 if (nxcol && rmargin) 210 xwidth += ewidth - col->width; 211 col->width = ewidth; 212 } 213 } 214 215 /* 216 * If there are any columns to maximize, find the total 217 * available width, deducting 3n margins between columns. 218 * Distribute the available width evenly. 219 */ 220 221 if (nxcol && rmargin) { 222 xwidth += 3*maxcol + 223 (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ? 224 2 : !!opts->lvert + !!opts->rvert); 225 if (rmargin <= offset + xwidth) 226 return; 227 xwidth = rmargin - offset - xwidth; 228 229 /* 230 * Emulate a bug in GNU tbl width calculation that 231 * manifests itself for large numbers of x-columns. 232 * Emulating it for 5 x-columns gives identical 233 * behaviour for up to 6 x-columns. 234 */ 235 236 if (nxcol == 5) { 237 quirkcol = xwidth % nxcol + 2; 238 if (quirkcol != 3 && quirkcol != 4) 239 quirkcol = -1; 240 } else 241 quirkcol = -1; 242 243 necol = 0; 244 ewidth = 0; 245 for (icol = 0; icol <= maxcol; icol++) { 246 col = tbl->cols + icol; 247 if ( ! (col->flags & TBL_CELL_WMAX)) 248 continue; 249 col->width = (double)xwidth * ++necol / nxcol 250 - ewidth + 0.4995; 251 if (necol == quirkcol) 252 col->width--; 253 ewidth += col->width; 254 } 255 } 256 } 257 258 static void 259 tblcalc_data(struct rofftbl *tbl, struct roffcol *col, 260 const struct tbl_opts *opts, const struct tbl_dat *dp, size_t mw) 261 { 262 size_t sz; 263 264 /* Branch down into data sub-types. */ 265 266 switch (dp->layout->pos) { 267 case TBL_CELL_HORIZ: 268 case TBL_CELL_DHORIZ: 269 sz = (*tbl->len)(1, tbl->arg); 270 if (col->width < sz) 271 col->width = sz; 272 break; 273 case TBL_CELL_LONG: 274 case TBL_CELL_CENTRE: 275 case TBL_CELL_LEFT: 276 case TBL_CELL_RIGHT: 277 tblcalc_literal(tbl, col, dp, mw); 278 break; 279 case TBL_CELL_NUMBER: 280 tblcalc_number(tbl, col, opts, dp); 281 break; 282 case TBL_CELL_DOWN: 283 break; 284 default: 285 abort(); 286 } 287 } 288 289 static void 290 tblcalc_literal(struct rofftbl *tbl, struct roffcol *col, 291 const struct tbl_dat *dp, size_t mw) 292 { 293 const char *str; /* Beginning of the first line. */ 294 const char *beg; /* Beginning of the current line. */ 295 char *end; /* End of the current line. */ 296 size_t lsz; /* Length of the current line. */ 297 size_t wsz; /* Length of the current word. */ 298 299 if (dp->string == NULL || *dp->string == '\0') 300 return; 301 str = mw ? mandoc_strdup(dp->string) : dp->string; 302 lsz = 0; 303 for (beg = str; beg != NULL && *beg != '\0'; beg = end) { 304 end = mw ? strchr(beg, ' ') : NULL; 305 if (end != NULL) { 306 *end++ = '\0'; 307 while (*end == ' ') 308 end++; 309 } 310 wsz = (*tbl->slen)(beg, tbl->arg); 311 if (mw && lsz && lsz + 1 + wsz <= mw) 312 lsz += 1 + wsz; 313 else 314 lsz = wsz; 315 if (col->width < lsz) 316 col->width = lsz; 317 } 318 if (mw) 319 free((void *)str); 320 } 321 322 static void 323 tblcalc_number(struct rofftbl *tbl, struct roffcol *col, 324 const struct tbl_opts *opts, const struct tbl_dat *dp) 325 { 326 const char *cp, *lastdigit, *lastpoint; 327 size_t intsz, totsz; 328 char buf[2]; 329 330 if (dp->string == NULL || *dp->string == '\0') 331 return; 332 333 /* 334 * Find the last digit and 335 * the last decimal point that is adjacent to a digit. 336 * The alignment indicator "\&" overrides everything. 337 */ 338 339 lastdigit = lastpoint = NULL; 340 for (cp = dp->string; cp[0] != '\0'; cp++) { 341 if (cp[0] == '\\' && cp[1] == '&') { 342 lastdigit = lastpoint = cp; 343 break; 344 } else if (cp[0] == opts->decimal && 345 (isdigit((unsigned char)cp[1]) || 346 (cp > dp->string && isdigit((unsigned char)cp[-1])))) 347 lastpoint = cp; 348 else if (isdigit((unsigned char)cp[0])) 349 lastdigit = cp; 350 } 351 352 /* Not a number, treat as a literal string. */ 353 354 totsz = (*tbl->slen)(dp->string, tbl->arg); 355 if (lastdigit == NULL) { 356 if (col->width < totsz) 357 col->width = totsz; 358 return; 359 } 360 361 /* Measure the width of the integer part. */ 362 363 if (lastpoint == NULL) 364 lastpoint = lastdigit + 1; 365 intsz = 0; 366 buf[1] = '\0'; 367 for (cp = dp->string; cp < lastpoint; cp++) { 368 buf[0] = cp[0]; 369 intsz += (*tbl->slen)(buf, tbl->arg); 370 } 371 372 /* 373 * If this number has more integer digits than all numbers 374 * seen on earlier lines, shift them all to the right. 375 * If it has fewer, shift this number to the right. 376 */ 377 378 if (intsz > col->decimal) { 379 col->nwidth += intsz - col->decimal; 380 col->decimal = intsz; 381 } else 382 totsz += col->decimal - intsz; 383 384 /* Update the maximum total width seen so far. */ 385 386 if (totsz > col->nwidth) 387 col->nwidth = totsz; 388 } 389