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