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