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