1 /* $Id: out.c,v 1.30 2011/01/05 15:37:23 kristaps Exp $ */ 2 /* 3 * Copyright (c) 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 #ifdef HAVE_CONFIG_H 18 #include "config.h" 19 #endif 20 21 #include <sys/types.h> 22 23 #include <assert.h> 24 #include <ctype.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <time.h> 29 30 #include "mandoc.h" 31 #include "out.h" 32 33 static void tblcalc_data(struct rofftbl *, struct roffcol *, 34 const struct tbl *, const struct tbl_dat *); 35 static void tblcalc_literal(struct rofftbl *, struct roffcol *, 36 const struct tbl_dat *); 37 static void tblcalc_number(struct rofftbl *, struct roffcol *, 38 const struct tbl *, const struct tbl_dat *); 39 40 /* 41 * Convert a `scaling unit' to a consistent form, or fail. Scaling 42 * units are documented in groff.7, mdoc.7, man.7. 43 */ 44 int 45 a2roffsu(const char *src, struct roffsu *dst, enum roffscale def) 46 { 47 char buf[BUFSIZ], hasd; 48 int i; 49 enum roffscale unit; 50 51 if ('\0' == *src) 52 return(0); 53 54 i = hasd = 0; 55 56 switch (*src) { 57 case ('+'): 58 src++; 59 break; 60 case ('-'): 61 buf[i++] = *src++; 62 break; 63 default: 64 break; 65 } 66 67 if ('\0' == *src) 68 return(0); 69 70 while (i < BUFSIZ) { 71 if ( ! isdigit((u_char)*src)) { 72 if ('.' != *src) 73 break; 74 else if (hasd) 75 break; 76 else 77 hasd = 1; 78 } 79 buf[i++] = *src++; 80 } 81 82 if (BUFSIZ == i || (*src && *(src + 1))) 83 return(0); 84 85 buf[i] = '\0'; 86 87 switch (*src) { 88 case ('c'): 89 unit = SCALE_CM; 90 break; 91 case ('i'): 92 unit = SCALE_IN; 93 break; 94 case ('P'): 95 unit = SCALE_PC; 96 break; 97 case ('p'): 98 unit = SCALE_PT; 99 break; 100 case ('f'): 101 unit = SCALE_FS; 102 break; 103 case ('v'): 104 unit = SCALE_VS; 105 break; 106 case ('m'): 107 unit = SCALE_EM; 108 break; 109 case ('\0'): 110 if (SCALE_MAX == def) 111 return(0); 112 unit = SCALE_BU; 113 break; 114 case ('u'): 115 unit = SCALE_BU; 116 break; 117 case ('M'): 118 unit = SCALE_MM; 119 break; 120 case ('n'): 121 unit = SCALE_EN; 122 break; 123 default: 124 return(0); 125 } 126 127 /* FIXME: do this in the caller. */ 128 if ((dst->scale = atof(buf)) < 0) 129 dst->scale = 0; 130 dst->unit = unit; 131 return(1); 132 } 133 134 135 /* 136 * Correctly writes the time in nroff form, which differs from standard 137 * form in that a space isn't printed in lieu of the extra %e field for 138 * single-digit dates. 139 */ 140 void 141 time2a(time_t t, char *dst, size_t sz) 142 { 143 struct tm tm; 144 char buf[5]; 145 char *p; 146 size_t nsz; 147 148 assert(sz > 1); 149 localtime_r(&t, &tm); 150 151 p = dst; 152 nsz = 0; 153 154 dst[0] = '\0'; 155 156 if (0 == (nsz = strftime(p, sz, "%B ", &tm))) 157 return; 158 159 p += (int)nsz; 160 sz -= nsz; 161 162 if (0 == strftime(buf, sizeof(buf), "%e, ", &tm)) 163 return; 164 165 nsz = strlcat(p, buf + (' ' == buf[0] ? 1 : 0), sz); 166 167 if (nsz >= sz) 168 return; 169 170 p += (int)nsz; 171 sz -= nsz; 172 173 (void)strftime(p, sz, "%Y", &tm); 174 } 175 176 177 int 178 a2roffdeco(enum roffdeco *d, const char **word, size_t *sz) 179 { 180 int i, j, lim; 181 char term, c; 182 const char *wp; 183 enum roffdeco dd; 184 185 *d = DECO_NONE; 186 lim = i = 0; 187 term = '\0'; 188 wp = *word; 189 190 switch ((c = wp[i++])) { 191 case ('('): 192 *d = DECO_SPECIAL; 193 lim = 2; 194 break; 195 case ('F'): 196 /* FALLTHROUGH */ 197 case ('f'): 198 *d = 'F' == c ? DECO_FFONT : DECO_FONT; 199 200 switch (wp[i++]) { 201 case ('('): 202 lim = 2; 203 break; 204 case ('['): 205 term = ']'; 206 break; 207 case ('3'): 208 /* FALLTHROUGH */ 209 case ('B'): 210 *d = DECO_BOLD; 211 return(i); 212 case ('2'): 213 /* FALLTHROUGH */ 214 case ('I'): 215 *d = DECO_ITALIC; 216 return(i); 217 case ('P'): 218 *d = DECO_PREVIOUS; 219 return(i); 220 case ('1'): 221 /* FALLTHROUGH */ 222 case ('R'): 223 *d = DECO_ROMAN; 224 return(i); 225 default: 226 i--; 227 lim = 1; 228 break; 229 } 230 break; 231 case ('k'): 232 /* FALLTHROUGH */ 233 case ('M'): 234 /* FALLTHROUGH */ 235 case ('m'): 236 /* FALLTHROUGH */ 237 case ('*'): 238 if ('*' == c) 239 *d = DECO_RESERVED; 240 241 switch (wp[i++]) { 242 case ('('): 243 lim = 2; 244 break; 245 case ('['): 246 term = ']'; 247 break; 248 default: 249 i--; 250 lim = 1; 251 break; 252 } 253 break; 254 case ('h'): 255 /* FALLTHROUGH */ 256 case ('v'): 257 /* FALLTHROUGH */ 258 case ('s'): 259 j = 0; 260 if ('+' == wp[i] || '-' == wp[i]) { 261 i++; 262 j = 1; 263 } 264 265 switch (wp[i++]) { 266 case ('('): 267 lim = 2; 268 break; 269 case ('['): 270 term = ']'; 271 break; 272 case ('\''): 273 term = '\''; 274 break; 275 case ('0'): 276 j = 1; 277 /* FALLTHROUGH */ 278 default: 279 i--; 280 lim = 1; 281 break; 282 } 283 284 if ('+' == wp[i] || '-' == wp[i]) { 285 if (j) 286 return(i); 287 i++; 288 } 289 290 /* Handle embedded numerical subexp or escape. */ 291 292 if ('(' == wp[i]) { 293 while (wp[i] && ')' != wp[i]) 294 if ('\\' == wp[i++]) { 295 /* Handle embedded escape. */ 296 *word = &wp[i]; 297 i += a2roffdeco(&dd, word, sz); 298 } 299 300 if (')' == wp[i++]) 301 break; 302 303 *d = DECO_NONE; 304 return(i - 1); 305 } else if ('\\' == wp[i]) { 306 *word = &wp[++i]; 307 i += a2roffdeco(&dd, word, sz); 308 } 309 310 break; 311 case ('['): 312 *d = DECO_SPECIAL; 313 term = ']'; 314 break; 315 case ('c'): 316 *d = DECO_NOSPACE; 317 return(i); 318 case ('z'): 319 *d = DECO_NONE; 320 if ('\\' == wp[i]) { 321 *word = &wp[++i]; 322 return(i + a2roffdeco(&dd, word, sz)); 323 } else 324 lim = 1; 325 break; 326 case ('o'): 327 /* FALLTHROUGH */ 328 case ('w'): 329 if ('\'' == wp[i++]) { 330 term = '\''; 331 break; 332 } 333 /* FALLTHROUGH */ 334 default: 335 *d = DECO_SSPECIAL; 336 i--; 337 lim = 1; 338 break; 339 } 340 341 assert(term || lim); 342 *word = &wp[i]; 343 344 if (term) { 345 j = i; 346 while (wp[i] && wp[i] != term) 347 i++; 348 if ('\0' == wp[i]) { 349 *d = DECO_NONE; 350 return(i); 351 } 352 353 assert(i >= j); 354 *sz = (size_t)(i - j); 355 356 return(i + 1); 357 } 358 359 assert(lim > 0); 360 *sz = (size_t)lim; 361 362 for (j = 0; wp[i] && j < lim; j++) 363 i++; 364 if (j < lim) 365 *d = DECO_NONE; 366 367 return(i); 368 } 369 370 /* 371 * Calculate the abstract widths and decimal positions of columns in a 372 * table. This routine allocates the columns structures then runs over 373 * all rows and cells in the table. The function pointers in "tbl" are 374 * used for the actual width calculations. 375 */ 376 void 377 tblcalc(struct rofftbl *tbl, const struct tbl_span *sp) 378 { 379 const struct tbl_dat *dp; 380 const struct tbl_head *hp; 381 struct roffcol *col; 382 383 /* 384 * Allocate the master column specifiers. These will hold the 385 * widths and decimal positions for all cells in the column. It 386 * must be freed and nullified by the caller. 387 */ 388 389 assert(NULL == tbl->cols); 390 tbl->cols = calloc(sp->tbl->cols, sizeof(struct roffcol)); 391 392 hp = sp->head; 393 394 for ( ; sp; sp = sp->next) { 395 if (TBL_SPAN_DATA != sp->pos) 396 continue; 397 /* 398 * Account for the data cells in the layout, matching it 399 * to data cells in the data section. 400 */ 401 for (dp = sp->first; dp; dp = dp->next) { 402 if (NULL == dp->layout) 403 continue; 404 col = &tbl->cols[dp->layout->head->ident]; 405 tblcalc_data(tbl, col, sp->tbl, dp); 406 } 407 } 408 409 /* 410 * Calculate width of the spanners. These get one space for a 411 * vertical line, two for a double-vertical line. 412 */ 413 414 for ( ; hp; hp = hp->next) { 415 col = &tbl->cols[hp->ident]; 416 switch (hp->pos) { 417 case (TBL_HEAD_VERT): 418 col->width = (*tbl->len)(1, tbl->arg); 419 break; 420 case (TBL_HEAD_DVERT): 421 col->width = (*tbl->len)(2, tbl->arg); 422 break; 423 default: 424 break; 425 } 426 } 427 } 428 429 static void 430 tblcalc_data(struct rofftbl *tbl, struct roffcol *col, 431 const struct tbl *tp, const struct tbl_dat *dp) 432 { 433 size_t sz; 434 435 /* Branch down into data sub-types. */ 436 437 switch (dp->layout->pos) { 438 case (TBL_CELL_HORIZ): 439 /* FALLTHROUGH */ 440 case (TBL_CELL_DHORIZ): 441 sz = (*tbl->len)(1, tbl->arg); 442 if (col->width < sz) 443 col->width = sz; 444 break; 445 case (TBL_CELL_LONG): 446 /* FALLTHROUGH */ 447 case (TBL_CELL_CENTRE): 448 /* FALLTHROUGH */ 449 case (TBL_CELL_LEFT): 450 /* FALLTHROUGH */ 451 case (TBL_CELL_RIGHT): 452 tblcalc_literal(tbl, col, dp); 453 break; 454 case (TBL_CELL_NUMBER): 455 tblcalc_number(tbl, col, tp, dp); 456 break; 457 default: 458 abort(); 459 /* NOTREACHED */ 460 } 461 } 462 463 static void 464 tblcalc_literal(struct rofftbl *tbl, struct roffcol *col, 465 const struct tbl_dat *dp) 466 { 467 size_t sz, bufsz, spsz; 468 469 /* 470 * Calculate our width and use the spacing, with a minimum 471 * spacing dictated by position (centre, e.g,. gets a space on 472 * either side, while right/left get a single adjacent space). 473 */ 474 475 sz = bufsz = spsz = 0; 476 if (dp->string) 477 sz = (*tbl->slen)(dp->string, tbl->arg); 478 479 assert(dp->layout); 480 switch (dp->layout->pos) { 481 case (TBL_CELL_LONG): 482 /* FALLTHROUGH */ 483 case (TBL_CELL_CENTRE): 484 bufsz = (*tbl->len)(2, tbl->arg); 485 break; 486 default: 487 bufsz = (*tbl->len)(1, tbl->arg); 488 break; 489 } 490 491 if (dp->layout->spacing) { 492 spsz = (*tbl->len)(dp->layout->spacing, tbl->arg); 493 bufsz = bufsz > spsz ? bufsz : spsz; 494 } 495 496 sz += bufsz; 497 if (col->width < sz) 498 col->width = sz; 499 } 500 501 static void 502 tblcalc_number(struct rofftbl *tbl, struct roffcol *col, 503 const struct tbl *tp, const struct tbl_dat *dp) 504 { 505 int i; 506 size_t sz, psz, ssz, d; 507 char *cp; 508 const char *str; 509 char buf[2]; 510 511 /* TODO: use spacing modifier. */ 512 513 /* 514 * First calculate number width and decimal place (last + 1 for 515 * no-decimal numbers). If the stored decimal is subsequent 516 * ours, make our size longer by that difference 517 * (right-"shifting"); similarly, if ours is subsequent the 518 * stored, then extend the stored size by the difference. 519 * Finally, re-assign the stored values. 520 */ 521 522 str = ""; 523 if (dp->string) 524 str = dp->string; 525 526 sz = (*tbl->slen)(str, tbl->arg); 527 528 buf[0] = tp->decimal; 529 buf[1] = '\0'; 530 531 psz = (*tbl->slen)(buf, tbl->arg); 532 533 if (NULL != (cp = strchr(str, tp->decimal))) { 534 buf[1] = '\0'; 535 for (ssz = 0, i = 0; cp != &str[i]; i++) { 536 buf[0] = str[i]; 537 ssz += (*tbl->slen)(buf, tbl->arg); 538 } 539 d = ssz + psz; 540 } else 541 d = sz + psz; 542 543 /* Padding. */ 544 545 sz += (*tbl->len)(2, tbl->arg); 546 d += (*tbl->len)(1, tbl->arg); 547 548 /* Adjust the settings for this column. */ 549 550 if (col->decimal > d) { 551 sz += col->decimal - d; 552 d = col->decimal; 553 } else 554 col->width += d - col->decimal; 555 556 if (sz > col->width) 557 col->width = sz; 558 if (d > col->decimal) 559 col->decimal = d; 560 } 561 562 563