1 /* $Id: tbl_term.c,v 1.57 2017/07/31 16:14:10 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2011,2012,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 <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 27 #include "mandoc.h" 28 #include "out.h" 29 #include "term.h" 30 31 #define IS_HORIZ(cp) ((cp)->pos == TBL_CELL_HORIZ || \ 32 (cp)->pos == TBL_CELL_DHORIZ) 33 34 static size_t term_tbl_len(size_t, void *); 35 static size_t term_tbl_strlen(const char *, void *); 36 static size_t term_tbl_sulen(const struct roffsu *, void *); 37 static void tbl_char(struct termp *, char, size_t); 38 static void tbl_data(struct termp *, const struct tbl_opts *, 39 const struct tbl_cell *, 40 const struct tbl_dat *, 41 const struct roffcol *); 42 static void tbl_literal(struct termp *, const struct tbl_dat *, 43 const struct roffcol *); 44 static void tbl_number(struct termp *, const struct tbl_opts *, 45 const struct tbl_dat *, 46 const struct roffcol *); 47 static void tbl_hrule(struct termp *, const struct tbl_span *, int); 48 static void tbl_word(struct termp *, const struct tbl_dat *); 49 50 51 static size_t 52 term_tbl_sulen(const struct roffsu *su, void *arg) 53 { 54 int i; 55 56 i = term_hen((const struct termp *)arg, su); 57 return i > 0 ? i : 0; 58 } 59 60 static size_t 61 term_tbl_strlen(const char *p, void *arg) 62 { 63 return term_strlen((const struct termp *)arg, p); 64 } 65 66 static size_t 67 term_tbl_len(size_t sz, void *arg) 68 { 69 return term_len((const struct termp *)arg, sz); 70 } 71 72 void 73 term_tbl(struct termp *tp, const struct tbl_span *sp) 74 { 75 const struct tbl_cell *cp, *cpn, *cpp; 76 const struct tbl_dat *dp; 77 static size_t offset; 78 size_t coloff, tsz; 79 int ic, horiz, spans, vert, more; 80 char fc; 81 82 /* Inhibit printing of spaces: we do padding ourselves. */ 83 84 tp->flags |= TERMP_NOSPACE | TERMP_NONOSPACE; 85 86 /* 87 * The first time we're invoked for a given table block, 88 * calculate the table widths and decimal positions. 89 */ 90 91 if (tp->tbl.cols == NULL) { 92 tp->tbl.len = term_tbl_len; 93 tp->tbl.slen = term_tbl_strlen; 94 tp->tbl.sulen = term_tbl_sulen; 95 tp->tbl.arg = tp; 96 97 tblcalc(&tp->tbl, sp, tp->tcol->offset, tp->tcol->rmargin); 98 99 /* Tables leak .ta settings to subsequent text. */ 100 101 term_tab_set(tp, NULL); 102 coloff = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) || 103 sp->opts->lvert; 104 for (ic = 0; ic < sp->opts->cols; ic++) { 105 coloff += tp->tbl.cols[ic].width; 106 term_tab_iset(coloff); 107 coloff += tp->tbl.cols[ic].spacing; 108 } 109 110 /* Center the table as a whole. */ 111 112 offset = tp->tcol->offset; 113 if (sp->opts->opts & TBL_OPT_CENTRE) { 114 tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) 115 ? 2 : !!sp->opts->lvert + !!sp->opts->rvert; 116 for (ic = 0; ic + 1 < sp->opts->cols; ic++) 117 tsz += tp->tbl.cols[ic].width + 118 tp->tbl.cols[ic].spacing; 119 if (sp->opts->cols) 120 tsz += tp->tbl.cols[sp->opts->cols - 1].width; 121 if (offset + tsz > tp->tcol->rmargin) 122 tsz -= 1; 123 tp->tcol->offset = offset + tp->tcol->rmargin > tsz ? 124 (offset + tp->tcol->rmargin - tsz) / 2 : 0; 125 } 126 127 /* Horizontal frame at the start of boxed tables. */ 128 129 if (sp->opts->opts & TBL_OPT_DBOX) 130 tbl_hrule(tp, sp, 3); 131 if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) 132 tbl_hrule(tp, sp, 2); 133 } 134 135 /* Set up the columns. */ 136 137 tp->flags |= TERMP_MULTICOL; 138 horiz = 0; 139 switch (sp->pos) { 140 case TBL_SPAN_HORIZ: 141 case TBL_SPAN_DHORIZ: 142 horiz = 1; 143 term_setcol(tp, 1); 144 break; 145 case TBL_SPAN_DATA: 146 term_setcol(tp, sp->opts->cols + 2); 147 coloff = tp->tcol->offset; 148 149 /* Set up a column for a left vertical frame. */ 150 151 if (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) || 152 sp->opts->lvert) 153 coloff++; 154 tp->tcol->rmargin = coloff; 155 156 /* Set up the data columns. */ 157 158 dp = sp->first; 159 spans = 0; 160 for (ic = 0; ic < sp->opts->cols; ic++) { 161 if (spans == 0) { 162 tp->tcol++; 163 tp->tcol->offset = coloff; 164 } 165 coloff += tp->tbl.cols[ic].width; 166 tp->tcol->rmargin = coloff; 167 if (ic + 1 < sp->opts->cols) 168 coloff += tp->tbl.cols[ic].spacing; 169 if (spans) { 170 spans--; 171 continue; 172 } 173 if (dp == NULL) 174 continue; 175 spans = dp->spans; 176 if (ic || sp->layout->first->pos != TBL_CELL_SPAN) 177 dp = dp->next; 178 } 179 180 /* Set up a column for a right vertical frame. */ 181 182 tp->tcol++; 183 tp->tcol->offset = coloff + 1; 184 tp->tcol->rmargin = tp->maxrmargin; 185 186 /* Spans may have reduced the number of columns. */ 187 188 tp->lasttcol = tp->tcol - tp->tcols; 189 190 /* Fill the buffers for all data columns. */ 191 192 tp->tcol = tp->tcols; 193 cp = cpn = sp->layout->first; 194 dp = sp->first; 195 spans = 0; 196 for (ic = 0; ic < sp->opts->cols; ic++) { 197 if (cpn != NULL) { 198 cp = cpn; 199 cpn = cpn->next; 200 } 201 if (spans) { 202 spans--; 203 continue; 204 } 205 tp->tcol++; 206 tp->col = 0; 207 tbl_data(tp, sp->opts, cp, dp, tp->tbl.cols + ic); 208 if (dp == NULL) 209 continue; 210 spans = dp->spans; 211 if (cp->pos != TBL_CELL_SPAN) 212 dp = dp->next; 213 } 214 break; 215 } 216 217 do { 218 /* Print the vertical frame at the start of each row. */ 219 220 tp->tcol = tp->tcols; 221 fc = '\0'; 222 if (sp->layout->vert || 223 (sp->next != NULL && sp->next->layout->vert && 224 sp->next->pos == TBL_SPAN_DATA) || 225 (sp->prev != NULL && sp->prev->layout->vert && 226 (horiz || (IS_HORIZ(sp->layout->first) && 227 !IS_HORIZ(sp->prev->layout->first)))) || 228 sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)) 229 fc = horiz || IS_HORIZ(sp->layout->first) ? '+' : '|'; 230 else if (horiz && sp->opts->lvert) 231 fc = '-'; 232 if (fc != '\0') { 233 (*tp->advance)(tp, tp->tcols->offset); 234 (*tp->letter)(tp, fc); 235 tp->viscol = tp->tcol->offset + 1; 236 } 237 238 /* Print the data cells. */ 239 240 more = 0; 241 if (horiz) { 242 tbl_hrule(tp, sp, 0); 243 term_flushln(tp); 244 } else { 245 cp = sp->layout->first; 246 cpn = sp->next == NULL ? NULL : 247 sp->next->layout->first; 248 cpp = sp->prev == NULL ? NULL : 249 sp->prev->layout->first; 250 dp = sp->first; 251 spans = 0; 252 for (ic = 0; ic < sp->opts->cols; ic++) { 253 254 /* 255 * Figure out whether to print a 256 * vertical line after this cell 257 * and advance to next layout cell. 258 */ 259 260 if (cp != NULL) { 261 vert = cp->vert; 262 switch (cp->pos) { 263 case TBL_CELL_HORIZ: 264 fc = '-'; 265 break; 266 case TBL_CELL_DHORIZ: 267 fc = '='; 268 break; 269 default: 270 fc = ' '; 271 break; 272 } 273 } else { 274 vert = 0; 275 fc = ' '; 276 } 277 if (cpp != NULL) { 278 if (vert == 0 && 279 cp != NULL && 280 ((IS_HORIZ(cp) && 281 !IS_HORIZ(cpp)) || 282 (cp->next != NULL && 283 cpp->next != NULL && 284 IS_HORIZ(cp->next) && 285 !IS_HORIZ(cpp->next)))) 286 vert = cpp->vert; 287 cpp = cpp->next; 288 } 289 if (vert == 0 && 290 sp->opts->opts & TBL_OPT_ALLBOX) 291 vert = 1; 292 if (cpn != NULL) { 293 if (vert == 0) 294 vert = cpn->vert; 295 cpn = cpn->next; 296 } 297 if (cp != NULL) 298 cp = cp->next; 299 300 /* 301 * Skip later cells in a span, 302 * figure out whether to start a span, 303 * and advance to next data cell. 304 */ 305 306 if (spans) { 307 spans--; 308 continue; 309 } 310 if (dp != NULL) { 311 spans = dp->spans; 312 if (ic || sp->layout->first->pos 313 != TBL_CELL_SPAN) 314 dp = dp->next; 315 } 316 317 /* 318 * Print one line of text in the cell 319 * and remember whether there is more. 320 */ 321 322 tp->tcol++; 323 if (tp->tcol->col < tp->tcol->lastcol) 324 term_flushln(tp); 325 if (tp->tcol->col < tp->tcol->lastcol) 326 more = 1; 327 328 /* 329 * Vertical frames between data cells, 330 * but not after the last column. 331 */ 332 333 if (fc == ' ' && ((vert == 0 && 334 (cp == NULL || !IS_HORIZ(cp))) || 335 tp->tcol + 1 == tp->tcols + tp->lasttcol)) 336 continue; 337 338 if (tp->viscol < tp->tcol->rmargin) { 339 (*tp->advance)(tp, tp->tcol->rmargin 340 - tp->viscol); 341 tp->viscol = tp->tcol->rmargin; 342 } 343 while (tp->viscol < tp->tcol->rmargin + 344 tp->tbl.cols[ic].spacing / 2) { 345 (*tp->letter)(tp, fc); 346 tp->viscol++; 347 } 348 349 if (tp->tcol + 1 == tp->tcols + tp->lasttcol) 350 continue; 351 352 if (fc == ' ' && cp != NULL) { 353 switch (cp->pos) { 354 case TBL_CELL_HORIZ: 355 fc = '-'; 356 break; 357 case TBL_CELL_DHORIZ: 358 fc = '='; 359 break; 360 default: 361 break; 362 } 363 } 364 if (tp->tbl.cols[ic].spacing) { 365 (*tp->letter)(tp, fc == ' ' ? '|' : 366 vert ? '+' : fc); 367 tp->viscol++; 368 } 369 370 if (fc != ' ') { 371 if (cp != NULL && 372 cp->pos == TBL_CELL_HORIZ) 373 fc = '-'; 374 else if (cp != NULL && 375 cp->pos == TBL_CELL_DHORIZ) 376 fc = '='; 377 else 378 fc = ' '; 379 } 380 if (tp->tbl.cols[ic].spacing > 2 && 381 (vert > 1 || fc != ' ')) { 382 (*tp->letter)(tp, fc == ' ' ? '|' : 383 vert > 1 ? '+' : fc); 384 tp->viscol++; 385 } 386 } 387 } 388 389 /* Print the vertical frame at the end of each row. */ 390 391 fc = '\0'; 392 if ((sp->layout->last->vert && 393 sp->layout->last->col + 1 == sp->opts->cols) || 394 (sp->next != NULL && 395 sp->next->layout->last->vert && 396 sp->next->layout->last->col + 1 == sp->opts->cols) || 397 (sp->prev != NULL && 398 sp->prev->layout->last->vert && 399 sp->prev->layout->last->col + 1 == sp->opts->cols && 400 (horiz || (IS_HORIZ(sp->layout->last) && 401 !IS_HORIZ(sp->prev->layout->last)))) || 402 (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX))) 403 fc = horiz || IS_HORIZ(sp->layout->last) ? '+' : '|'; 404 else if (horiz && sp->opts->rvert) 405 fc = '-'; 406 if (fc != '\0') { 407 if (horiz == 0 && (IS_HORIZ(sp->layout->last) == 0 || 408 sp->layout->last->col + 1 < sp->opts->cols)) { 409 tp->tcol++; 410 (*tp->advance)(tp, 411 tp->tcol->offset > tp->viscol ? 412 tp->tcol->offset - tp->viscol : 1); 413 } 414 (*tp->letter)(tp, fc); 415 } 416 (*tp->endline)(tp); 417 tp->viscol = 0; 418 } while (more); 419 420 /* 421 * Clean up after this row. If it is the last line 422 * of the table, print the box line and clean up 423 * column data; otherwise, print the allbox line. 424 */ 425 426 term_setcol(tp, 1); 427 tp->flags &= ~TERMP_MULTICOL; 428 tp->tcol->rmargin = tp->maxrmargin; 429 if (sp->next == NULL) { 430 if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) { 431 tbl_hrule(tp, sp, 2); 432 tp->skipvsp = 1; 433 } 434 if (sp->opts->opts & TBL_OPT_DBOX) { 435 tbl_hrule(tp, sp, 3); 436 tp->skipvsp = 2; 437 } 438 assert(tp->tbl.cols); 439 free(tp->tbl.cols); 440 tp->tbl.cols = NULL; 441 tp->tcol->offset = offset; 442 } else if (horiz == 0 && sp->opts->opts & TBL_OPT_ALLBOX && 443 (sp->next == NULL || sp->next->pos == TBL_SPAN_DATA || 444 sp->next->next != NULL)) 445 tbl_hrule(tp, sp, 1); 446 447 tp->flags &= ~TERMP_NONOSPACE; 448 } 449 450 /* 451 * Kinds of horizontal rulers: 452 * 0: inside the table (single or double line with crossings) 453 * 1: inside the table (single or double line with crossings and ends) 454 * 2: inner frame (single line with crossings and ends) 455 * 3: outer frame (single line without crossings with ends) 456 */ 457 static void 458 tbl_hrule(struct termp *tp, const struct tbl_span *sp, int kind) 459 { 460 const struct tbl_cell *cp, *cpn, *cpp; 461 const struct roffcol *col; 462 int vert; 463 char line, cross; 464 465 line = (kind < 2 && TBL_SPAN_DHORIZ == sp->pos) ? '=' : '-'; 466 cross = (kind < 3) ? '+' : '-'; 467 468 if (kind) 469 term_word(tp, "+"); 470 cp = sp->layout->first; 471 cpp = kind || sp->prev == NULL ? NULL : sp->prev->layout->first; 472 if (cpp == cp) 473 cpp = NULL; 474 cpn = kind > 1 || sp->next == NULL ? NULL : sp->next->layout->first; 475 if (cpn == cp) 476 cpn = NULL; 477 for (;;) { 478 col = tp->tbl.cols + cp->col; 479 tbl_char(tp, line, col->width + col->spacing / 2); 480 vert = cp->vert; 481 if ((cp = cp->next) == NULL) 482 break; 483 if (cpp != NULL) { 484 if (vert < cpp->vert) 485 vert = cpp->vert; 486 cpp = cpp->next; 487 } 488 if (cpn != NULL) { 489 if (vert < cpn->vert) 490 vert = cpn->vert; 491 cpn = cpn->next; 492 } 493 if (sp->opts->opts & TBL_OPT_ALLBOX && !vert) 494 vert = 1; 495 if (col->spacing) 496 tbl_char(tp, vert ? cross : line, 1); 497 if (col->spacing > 2) 498 tbl_char(tp, vert > 1 ? cross : line, 1); 499 if (col->spacing > 4) 500 tbl_char(tp, line, (col->spacing - 3) / 2); 501 } 502 if (kind) { 503 term_word(tp, "+"); 504 term_flushln(tp); 505 } 506 } 507 508 static void 509 tbl_data(struct termp *tp, const struct tbl_opts *opts, 510 const struct tbl_cell *cp, const struct tbl_dat *dp, 511 const struct roffcol *col) 512 { 513 switch (cp->pos) { 514 case TBL_CELL_HORIZ: 515 tbl_char(tp, '-', col->width); 516 return; 517 case TBL_CELL_DHORIZ: 518 tbl_char(tp, '=', col->width); 519 return; 520 default: 521 break; 522 } 523 524 if (dp == NULL) 525 return; 526 527 switch (dp->pos) { 528 case TBL_DATA_NONE: 529 return; 530 case TBL_DATA_HORIZ: 531 case TBL_DATA_NHORIZ: 532 tbl_char(tp, '-', col->width); 533 return; 534 case TBL_DATA_NDHORIZ: 535 case TBL_DATA_DHORIZ: 536 tbl_char(tp, '=', col->width); 537 return; 538 default: 539 break; 540 } 541 542 switch (cp->pos) { 543 case TBL_CELL_LONG: 544 case TBL_CELL_CENTRE: 545 case TBL_CELL_LEFT: 546 case TBL_CELL_RIGHT: 547 tbl_literal(tp, dp, col); 548 break; 549 case TBL_CELL_NUMBER: 550 tbl_number(tp, opts, dp, col); 551 break; 552 case TBL_CELL_DOWN: 553 case TBL_CELL_SPAN: 554 break; 555 default: 556 abort(); 557 } 558 } 559 560 static void 561 tbl_char(struct termp *tp, char c, size_t len) 562 { 563 size_t i, sz; 564 char cp[2]; 565 566 cp[0] = c; 567 cp[1] = '\0'; 568 569 sz = term_strlen(tp, cp); 570 571 for (i = 0; i < len; i += sz) 572 term_word(tp, cp); 573 } 574 575 static void 576 tbl_literal(struct termp *tp, const struct tbl_dat *dp, 577 const struct roffcol *col) 578 { 579 size_t len, padl, padr, width; 580 int ic, spans; 581 582 assert(dp->string); 583 len = term_strlen(tp, dp->string); 584 width = col->width; 585 ic = dp->layout->col; 586 spans = dp->spans; 587 while (spans--) 588 width += tp->tbl.cols[++ic].width + 3; 589 590 padr = width > len ? width - len : 0; 591 padl = 0; 592 593 switch (dp->layout->pos) { 594 case TBL_CELL_LONG: 595 padl = term_len(tp, 1); 596 padr = padr > padl ? padr - padl : 0; 597 break; 598 case TBL_CELL_CENTRE: 599 if (2 > padr) 600 break; 601 padl = padr / 2; 602 padr -= padl; 603 break; 604 case TBL_CELL_RIGHT: 605 padl = padr; 606 padr = 0; 607 break; 608 default: 609 break; 610 } 611 612 tbl_char(tp, ASCII_NBRSP, padl); 613 tbl_word(tp, dp); 614 tbl_char(tp, ASCII_NBRSP, padr); 615 } 616 617 static void 618 tbl_number(struct termp *tp, const struct tbl_opts *opts, 619 const struct tbl_dat *dp, 620 const struct roffcol *col) 621 { 622 char *cp; 623 char buf[2]; 624 size_t sz, psz, ssz, d, padl; 625 int i; 626 627 /* 628 * See calc_data_number(). Left-pad by taking the offset of our 629 * and the maximum decimal; right-pad by the remaining amount. 630 */ 631 632 assert(dp->string); 633 634 sz = term_strlen(tp, dp->string); 635 636 buf[0] = opts->decimal; 637 buf[1] = '\0'; 638 639 psz = term_strlen(tp, buf); 640 641 if ((cp = strrchr(dp->string, opts->decimal)) != NULL) { 642 for (ssz = 0, i = 0; cp != &dp->string[i]; i++) { 643 buf[0] = dp->string[i]; 644 ssz += term_strlen(tp, buf); 645 } 646 d = ssz + psz; 647 } else 648 d = sz + psz; 649 650 if (col->decimal > d && col->width > sz) { 651 padl = col->decimal - d; 652 if (padl + sz > col->width) 653 padl = col->width - sz; 654 tbl_char(tp, ASCII_NBRSP, padl); 655 } else 656 padl = 0; 657 tbl_word(tp, dp); 658 if (col->width > sz + padl) 659 tbl_char(tp, ASCII_NBRSP, col->width - sz - padl); 660 } 661 662 static void 663 tbl_word(struct termp *tp, const struct tbl_dat *dp) 664 { 665 int prev_font; 666 667 prev_font = tp->fonti; 668 if (dp->layout->flags & TBL_CELL_BOLD) 669 term_fontpush(tp, TERMFONT_BOLD); 670 else if (dp->layout->flags & TBL_CELL_ITALIC) 671 term_fontpush(tp, TERMFONT_UNDER); 672 673 term_word(tp, dp->string); 674 675 term_fontpopq(tp, prev_font); 676 } 677