1 /* $OpenBSD: tbl_term.c,v 1.63 2021/08/10 12:36:42 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2011-2021 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 <ctype.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 26 #include "mandoc.h" 27 #include "tbl.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 35 static size_t term_tbl_len(size_t, void *); 36 static size_t term_tbl_strlen(const char *, void *); 37 static size_t term_tbl_sulen(const struct roffsu *, void *); 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_direct_border(struct termp *, int, size_t); 43 static void tbl_fill_border(struct termp *, int, size_t); 44 static void tbl_fill_char(struct termp *, char, size_t); 45 static void tbl_fill_string(struct termp *, const char *, size_t); 46 static void tbl_hrule(struct termp *, const struct tbl_span *, 47 const struct tbl_span *, const struct tbl_span *, 48 int); 49 static void tbl_literal(struct termp *, const struct tbl_dat *, 50 const struct roffcol *); 51 static void tbl_number(struct termp *, const struct tbl_opts *, 52 const struct tbl_dat *, 53 const struct roffcol *); 54 static void tbl_word(struct termp *, const struct tbl_dat *); 55 56 57 /* 58 * The following border-character tables are indexed 59 * by ternary (3-based) numbers, as opposed to binary or decimal. 60 * Each ternary digit describes the line width in one direction: 61 * 0 means no line, 1 single or light line, 2 double or heavy line. 62 */ 63 64 /* Positional values of the four directions. */ 65 #define BRIGHT 1 66 #define BDOWN 3 67 #define BLEFT (3 * 3) 68 #define BUP (3 * 3 * 3) 69 #define BHORIZ (BLEFT + BRIGHT) 70 71 /* Code points to use for each combination of widths. */ 72 static const int borders_utf8[81] = { 73 0x0020, 0x2576, 0x257a, /* 000 right */ 74 0x2577, 0x250c, 0x250d, /* 001 down */ 75 0x257b, 0x250e, 0x250f, /* 002 */ 76 0x2574, 0x2500, 0x257c, /* 010 left */ 77 0x2510, 0x252c, 0x252e, /* 011 left down */ 78 0x2512, 0x2530, 0x2532, /* 012 */ 79 0x2578, 0x257e, 0x2501, /* 020 left */ 80 0x2511, 0x252d, 0x252f, /* 021 left down */ 81 0x2513, 0x2531, 0x2533, /* 022 */ 82 0x2575, 0x2514, 0x2515, /* 100 up */ 83 0x2502, 0x251c, 0x251d, /* 101 up down */ 84 0x257d, 0x251f, 0x2522, /* 102 */ 85 0x2518, 0x2534, 0x2536, /* 110 up left */ 86 0x2524, 0x253c, 0x253e, /* 111 all */ 87 0x2527, 0x2541, 0x2546, /* 112 */ 88 0x2519, 0x2535, 0x2537, /* 120 up left */ 89 0x2525, 0x253d, 0x253f, /* 121 all */ 90 0x252a, 0x2545, 0x2548, /* 122 */ 91 0x2579, 0x2516, 0x2517, /* 200 up */ 92 0x257f, 0x251e, 0x2521, /* 201 up down */ 93 0x2503, 0x2520, 0x2523, /* 202 */ 94 0x251a, 0x2538, 0x253a, /* 210 up left */ 95 0x2526, 0x2540, 0x2544, /* 211 all */ 96 0x2528, 0x2542, 0x254a, /* 212 */ 97 0x251b, 0x2539, 0x253b, /* 220 up left */ 98 0x2529, 0x2543, 0x2547, /* 221 all */ 99 0x252b, 0x2549, 0x254b, /* 222 */ 100 }; 101 102 /* ASCII approximations for these code points, compatible with groff. */ 103 static const int borders_ascii[81] = { 104 ' ', '-', '=', /* 000 right */ 105 '|', '+', '+', /* 001 down */ 106 '|', '+', '+', /* 002 */ 107 '-', '-', '=', /* 010 left */ 108 '+', '+', '+', /* 011 left down */ 109 '+', '+', '+', /* 012 */ 110 '=', '=', '=', /* 020 left */ 111 '+', '+', '+', /* 021 left down */ 112 '+', '+', '+', /* 022 */ 113 '|', '+', '+', /* 100 up */ 114 '|', '+', '+', /* 101 up down */ 115 '|', '+', '+', /* 102 */ 116 '+', '+', '+', /* 110 up left */ 117 '+', '+', '+', /* 111 all */ 118 '+', '+', '+', /* 112 */ 119 '+', '+', '+', /* 120 up left */ 120 '+', '+', '+', /* 121 all */ 121 '+', '+', '+', /* 122 */ 122 '|', '+', '+', /* 200 up */ 123 '|', '+', '+', /* 201 up down */ 124 '|', '+', '+', /* 202 */ 125 '+', '+', '+', /* 210 up left */ 126 '+', '+', '+', /* 211 all */ 127 '+', '+', '+', /* 212 */ 128 '+', '+', '+', /* 220 up left */ 129 '+', '+', '+', /* 221 all */ 130 '+', '+', '+', /* 222 */ 131 }; 132 133 /* Either of the above according to the selected output encoding. */ 134 static const int *borders_locale; 135 136 137 static size_t 138 term_tbl_sulen(const struct roffsu *su, void *arg) 139 { 140 int i; 141 142 i = term_hen((const struct termp *)arg, su); 143 return i > 0 ? i : 0; 144 } 145 146 static size_t 147 term_tbl_strlen(const char *p, void *arg) 148 { 149 return term_strlen((const struct termp *)arg, p); 150 } 151 152 static size_t 153 term_tbl_len(size_t sz, void *arg) 154 { 155 return term_len((const struct termp *)arg, sz); 156 } 157 158 159 void 160 term_tbl(struct termp *tp, const struct tbl_span *sp) 161 { 162 const struct tbl_cell *cp, *cpn, *cpp, *cps; 163 const struct tbl_dat *dp; 164 static size_t offset; 165 size_t save_offset; 166 size_t coloff, tsz; 167 int hspans, ic, more; 168 int dvert, fc, horiz, lhori, rhori, uvert; 169 170 /* Inhibit printing of spaces: we do padding ourselves. */ 171 172 tp->flags |= TERMP_NOSPACE | TERMP_NONOSPACE; 173 save_offset = tp->tcol->offset; 174 175 /* 176 * The first time we're invoked for a given table block, 177 * calculate the table widths and decimal positions. 178 */ 179 180 if (tp->tbl.cols == NULL) { 181 borders_locale = tp->enc == TERMENC_UTF8 ? 182 borders_utf8 : borders_ascii; 183 184 tp->tbl.len = term_tbl_len; 185 tp->tbl.slen = term_tbl_strlen; 186 tp->tbl.sulen = term_tbl_sulen; 187 tp->tbl.arg = tp; 188 189 tblcalc(&tp->tbl, sp, tp->tcol->offset, tp->tcol->rmargin); 190 191 /* Center the table as a whole. */ 192 193 offset = tp->tcol->offset; 194 if (sp->opts->opts & TBL_OPT_CENTRE) { 195 tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) 196 ? 2 : !!sp->opts->lvert + !!sp->opts->rvert; 197 for (ic = 0; ic + 1 < sp->opts->cols; ic++) 198 tsz += tp->tbl.cols[ic].width + 199 tp->tbl.cols[ic].spacing; 200 if (sp->opts->cols) 201 tsz += tp->tbl.cols[sp->opts->cols - 1].width; 202 if (offset + tsz > tp->tcol->rmargin) 203 tsz -= 1; 204 offset = offset + tp->tcol->rmargin > tsz ? 205 (offset + tp->tcol->rmargin - tsz) / 2 : 0; 206 tp->tcol->offset = offset; 207 } 208 209 /* Horizontal frame at the start of boxed tables. */ 210 211 if (tp->enc == TERMENC_ASCII && 212 sp->opts->opts & TBL_OPT_DBOX) 213 tbl_hrule(tp, NULL, sp, sp, TBL_OPT_DBOX); 214 if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) 215 tbl_hrule(tp, NULL, sp, sp, TBL_OPT_BOX); 216 } 217 218 /* Set up the columns. */ 219 220 tp->flags |= TERMP_MULTICOL; 221 tp->tcol->offset = offset; 222 horiz = 0; 223 switch (sp->pos) { 224 case TBL_SPAN_HORIZ: 225 case TBL_SPAN_DHORIZ: 226 horiz = 1; 227 term_setcol(tp, 1); 228 break; 229 case TBL_SPAN_DATA: 230 term_setcol(tp, sp->opts->cols + 2); 231 coloff = tp->tcol->offset; 232 233 /* Set up a column for a left vertical frame. */ 234 235 if (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) || 236 sp->opts->lvert) 237 coloff++; 238 tp->tcol->rmargin = coloff; 239 240 /* Set up the data columns. */ 241 242 dp = sp->first; 243 hspans = 0; 244 for (ic = 0; ic < sp->opts->cols; ic++) { 245 if (hspans == 0) { 246 tp->tcol++; 247 tp->tcol->offset = coloff; 248 } 249 coloff += tp->tbl.cols[ic].width; 250 tp->tcol->rmargin = coloff; 251 if (ic + 1 < sp->opts->cols) 252 coloff += tp->tbl.cols[ic].spacing; 253 if (hspans) { 254 hspans--; 255 continue; 256 } 257 if (dp != NULL && 258 (ic || sp->layout->first->pos != TBL_CELL_SPAN)) { 259 hspans = dp->hspans; 260 dp = dp->next; 261 } 262 } 263 264 /* Set up a column for a right vertical frame. */ 265 266 tp->tcol++; 267 tp->tcol->offset = coloff + 1; 268 tp->tcol->rmargin = tp->maxrmargin; 269 270 /* Spans may have reduced the number of columns. */ 271 272 tp->lasttcol = tp->tcol - tp->tcols; 273 274 /* Fill the buffers for all data columns. */ 275 276 tp->tcol = tp->tcols; 277 cp = cpn = sp->layout->first; 278 dp = sp->first; 279 hspans = 0; 280 for (ic = 0; ic < sp->opts->cols; ic++) { 281 if (cpn != NULL) { 282 cp = cpn; 283 cpn = cpn->next; 284 } 285 if (hspans) { 286 hspans--; 287 continue; 288 } 289 tp->tcol++; 290 tp->col = 0; 291 tbl_data(tp, sp->opts, cp, dp, tp->tbl.cols + ic); 292 if (dp != NULL && 293 (ic || sp->layout->first->pos != TBL_CELL_SPAN)) { 294 hspans = dp->hspans; 295 dp = dp->next; 296 } 297 } 298 break; 299 } 300 301 do { 302 /* Print the vertical frame at the start of each row. */ 303 304 tp->tcol = tp->tcols; 305 uvert = dvert = sp->opts->opts & TBL_OPT_DBOX ? 2 : 306 sp->opts->opts & TBL_OPT_BOX ? 1 : 0; 307 if (sp->pos == TBL_SPAN_DATA && uvert < sp->layout->vert) 308 uvert = dvert = sp->layout->vert; 309 if (sp->next != NULL && sp->next->pos == TBL_SPAN_DATA && 310 dvert < sp->next->layout->vert) 311 dvert = sp->next->layout->vert; 312 if (sp->prev != NULL && uvert < sp->prev->layout->vert && 313 (horiz || (IS_HORIZ(sp->layout->first) && 314 !IS_HORIZ(sp->prev->layout->first)))) 315 uvert = sp->prev->layout->vert; 316 rhori = sp->pos == TBL_SPAN_DHORIZ || 317 (sp->first != NULL && sp->first->pos == TBL_DATA_DHORIZ) || 318 sp->layout->first->pos == TBL_CELL_DHORIZ ? 2 : 319 sp->pos == TBL_SPAN_HORIZ || 320 (sp->first != NULL && sp->first->pos == TBL_DATA_HORIZ) || 321 sp->layout->first->pos == TBL_CELL_HORIZ ? 1 : 0; 322 fc = BUP * uvert + BDOWN * dvert + BRIGHT * rhori; 323 if (uvert > 0 || dvert > 0 || (horiz && sp->opts->lvert)) { 324 (*tp->advance)(tp, tp->tcols->offset); 325 tp->viscol = tp->tcol->offset; 326 tbl_direct_border(tp, fc, 1); 327 } 328 329 /* Print the data cells. */ 330 331 more = 0; 332 if (horiz) 333 tbl_hrule(tp, sp->prev, sp, sp->next, 0); 334 else { 335 cp = sp->layout->first; 336 cpn = sp->next == NULL ? NULL : 337 sp->next->layout->first; 338 cpp = sp->prev == NULL ? NULL : 339 sp->prev->layout->first; 340 dp = sp->first; 341 hspans = 0; 342 for (ic = 0; ic < sp->opts->cols; ic++) { 343 344 /* 345 * Figure out whether to print a 346 * vertical line after this cell 347 * and advance to next layout cell. 348 */ 349 350 uvert = dvert = fc = 0; 351 if (cp != NULL) { 352 cps = cp; 353 while (cps->next != NULL && 354 cps->next->pos == TBL_CELL_SPAN) 355 cps = cps->next; 356 if (sp->pos == TBL_SPAN_DATA) 357 uvert = dvert = cps->vert; 358 switch (cp->pos) { 359 case TBL_CELL_HORIZ: 360 fc = BHORIZ; 361 break; 362 case TBL_CELL_DHORIZ: 363 fc = BHORIZ * 2; 364 break; 365 default: 366 break; 367 } 368 } 369 if (cpp != NULL) { 370 if (uvert < cpp->vert && 371 cp != NULL && 372 ((IS_HORIZ(cp) && 373 !IS_HORIZ(cpp)) || 374 (cp->next != NULL && 375 cpp->next != NULL && 376 IS_HORIZ(cp->next) && 377 !IS_HORIZ(cpp->next)))) 378 uvert = cpp->vert; 379 cpp = cpp->next; 380 } 381 if (sp->opts->opts & TBL_OPT_ALLBOX) { 382 if (uvert == 0) 383 uvert = 1; 384 if (dvert == 0) 385 dvert = 1; 386 } 387 if (cpn != NULL) { 388 if (dvert == 0 || 389 (dvert < cpn->vert && 390 tp->enc == TERMENC_UTF8)) 391 dvert = cpn->vert; 392 cpn = cpn->next; 393 } 394 395 lhori = (cp != NULL && 396 cp->pos == TBL_CELL_DHORIZ) || 397 (dp != NULL && 398 dp->pos == TBL_DATA_DHORIZ) ? 2 : 399 (cp != NULL && 400 cp->pos == TBL_CELL_HORIZ) || 401 (dp != NULL && 402 dp->pos == TBL_DATA_HORIZ) ? 1 : 0; 403 404 /* 405 * Skip later cells in a span, 406 * figure out whether to start a span, 407 * and advance to next data cell. 408 */ 409 410 if (hspans) { 411 hspans--; 412 cp = cp->next; 413 continue; 414 } 415 if (dp != NULL && (ic || 416 sp->layout->first->pos != TBL_CELL_SPAN)) { 417 hspans = dp->hspans; 418 dp = dp->next; 419 } 420 421 /* 422 * Print one line of text in the cell 423 * and remember whether there is more. 424 */ 425 426 tp->tcol++; 427 if (tp->tcol->col < tp->tcol->lastcol) 428 term_flushln(tp); 429 if (tp->tcol->col < tp->tcol->lastcol) 430 more = 1; 431 432 /* 433 * Vertical frames between data cells, 434 * but not after the last column. 435 */ 436 437 if (fc == 0 && 438 ((uvert == 0 && dvert == 0 && 439 cp != NULL && (cp->next == NULL || 440 !IS_HORIZ(cp->next))) || 441 tp->tcol + 1 == 442 tp->tcols + tp->lasttcol)) { 443 if (cp != NULL) 444 cp = cp->next; 445 continue; 446 } 447 448 if (tp->viscol < tp->tcol->rmargin) { 449 (*tp->advance)(tp, tp->tcol->rmargin 450 - tp->viscol); 451 tp->viscol = tp->tcol->rmargin; 452 } 453 while (tp->viscol < tp->tcol->rmargin + 454 tp->tbl.cols[ic].spacing / 2) 455 tbl_direct_border(tp, 456 BHORIZ * lhori, 1); 457 458 if (tp->tcol + 1 == tp->tcols + tp->lasttcol) 459 continue; 460 461 if (cp != NULL) 462 cp = cp->next; 463 464 rhori = (cp != NULL && 465 cp->pos == TBL_CELL_DHORIZ) || 466 (dp != NULL && 467 dp->pos == TBL_DATA_DHORIZ) ? 2 : 468 (cp != NULL && 469 cp->pos == TBL_CELL_HORIZ) || 470 (dp != NULL && 471 dp->pos == TBL_DATA_HORIZ) ? 1 : 0; 472 473 if (tp->tbl.cols[ic].spacing) 474 tbl_direct_border(tp, 475 BLEFT * lhori + BRIGHT * rhori + 476 BUP * uvert + BDOWN * dvert, 1); 477 478 if (tp->enc == TERMENC_UTF8) 479 uvert = dvert = 0; 480 481 if (tp->tbl.cols[ic].spacing > 2 && 482 (uvert > 1 || dvert > 1 || rhori)) 483 tbl_direct_border(tp, 484 BHORIZ * rhori + 485 BUP * (uvert > 1) + 486 BDOWN * (dvert > 1), 1); 487 } 488 } 489 490 /* Print the vertical frame at the end of each row. */ 491 492 uvert = dvert = sp->opts->opts & TBL_OPT_DBOX ? 2 : 493 sp->opts->opts & TBL_OPT_BOX ? 1 : 0; 494 if (sp->pos == TBL_SPAN_DATA && 495 uvert < sp->layout->last->vert && 496 sp->layout->last->col + 1 == sp->opts->cols) 497 uvert = dvert = sp->layout->last->vert; 498 if (sp->next != NULL && 499 dvert < sp->next->layout->last->vert && 500 sp->next->layout->last->col + 1 == sp->opts->cols) 501 dvert = sp->next->layout->last->vert; 502 if (sp->prev != NULL && 503 uvert < sp->prev->layout->last->vert && 504 sp->prev->layout->last->col + 1 == sp->opts->cols && 505 (horiz || (IS_HORIZ(sp->layout->last) && 506 !IS_HORIZ(sp->prev->layout->last)))) 507 uvert = sp->prev->layout->last->vert; 508 lhori = sp->pos == TBL_SPAN_DHORIZ || 509 (sp->last != NULL && 510 sp->last->pos == TBL_DATA_DHORIZ && 511 sp->last->layout->col + 1 == sp->opts->cols) || 512 (sp->layout->last->pos == TBL_CELL_DHORIZ && 513 sp->layout->last->col + 1 == sp->opts->cols) ? 2 : 514 sp->pos == TBL_SPAN_HORIZ || 515 (sp->last != NULL && 516 sp->last->pos == TBL_DATA_HORIZ && 517 sp->last->layout->col + 1 == sp->opts->cols) || 518 (sp->layout->last->pos == TBL_CELL_HORIZ && 519 sp->layout->last->col + 1 == sp->opts->cols) ? 1 : 0; 520 fc = BUP * uvert + BDOWN * dvert + BLEFT * lhori; 521 if (uvert > 0 || dvert > 0 || (horiz && sp->opts->rvert)) { 522 if (horiz == 0 && (IS_HORIZ(sp->layout->last) == 0 || 523 sp->layout->last->col + 1 < sp->opts->cols)) { 524 tp->tcol++; 525 do { 526 tbl_direct_border(tp, 527 BHORIZ * lhori, 1); 528 } while (tp->viscol < tp->tcol->offset); 529 } 530 tbl_direct_border(tp, fc, 1); 531 } 532 (*tp->endline)(tp); 533 tp->viscol = 0; 534 } while (more); 535 536 /* 537 * Clean up after this row. If it is the last line 538 * of the table, print the box line and clean up 539 * column data; otherwise, print the allbox line. 540 */ 541 542 term_setcol(tp, 1); 543 tp->flags &= ~TERMP_MULTICOL; 544 tp->tcol->rmargin = tp->maxrmargin; 545 if (sp->next == NULL) { 546 if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) { 547 tbl_hrule(tp, sp, sp, NULL, TBL_OPT_BOX); 548 tp->skipvsp = 1; 549 } 550 if (tp->enc == TERMENC_ASCII && 551 sp->opts->opts & TBL_OPT_DBOX) { 552 tbl_hrule(tp, sp, sp, NULL, TBL_OPT_DBOX); 553 tp->skipvsp = 2; 554 } 555 assert(tp->tbl.cols); 556 free(tp->tbl.cols); 557 tp->tbl.cols = NULL; 558 } else if (horiz == 0 && sp->opts->opts & TBL_OPT_ALLBOX && 559 (sp->next == NULL || sp->next->pos == TBL_SPAN_DATA || 560 sp->next->next != NULL)) 561 tbl_hrule(tp, sp, sp, sp->next, TBL_OPT_ALLBOX); 562 563 tp->tcol->offset = save_offset; 564 tp->flags &= ~TERMP_NONOSPACE; 565 } 566 567 static void 568 tbl_hrule(struct termp *tp, const struct tbl_span *spp, 569 const struct tbl_span *sp, const struct tbl_span *spn, int flags) 570 { 571 const struct tbl_cell *cpp; /* Layout cell above this line. */ 572 const struct tbl_cell *cp; /* Layout cell in this line. */ 573 const struct tbl_cell *cpn; /* Layout cell below this line. */ 574 const struct tbl_dat *dpn; /* Data cell below this line. */ 575 const struct roffcol *col; /* Contains width and spacing. */ 576 int opts; /* For the table as a whole. */ 577 int bw; /* Box line width. */ 578 int hw; /* Horizontal line width. */ 579 int lw, rw; /* Left and right line widths. */ 580 int uw, dw; /* Vertical line widths. */ 581 582 cpp = spp == NULL ? NULL : spp->layout->first; 583 cp = sp == NULL ? NULL : sp->layout->first; 584 cpn = spn == NULL ? NULL : spn->layout->first; 585 dpn = NULL; 586 if (spn != NULL) { 587 if (spn->pos == TBL_SPAN_DATA) 588 dpn = spn->first; 589 else if (spn->next != NULL) 590 dpn = spn->next->first; 591 } 592 opts = sp->opts->opts; 593 bw = opts & TBL_OPT_DBOX ? (tp->enc == TERMENC_UTF8 ? 2 : 1) : 594 opts & (TBL_OPT_BOX | TBL_OPT_ALLBOX) ? 1 : 0; 595 hw = flags == TBL_OPT_DBOX || flags == TBL_OPT_BOX ? bw : 596 sp->pos == TBL_SPAN_DHORIZ ? 2 : 1; 597 598 /* Print the left end of the line. */ 599 600 if (tp->viscol == 0) { 601 (*tp->advance)(tp, tp->tcols->offset); 602 tp->viscol = tp->tcols->offset; 603 } 604 if (flags != 0) 605 tbl_direct_border(tp, 606 (spp == NULL ? 0 : BUP * bw) + 607 (spn == NULL ? 0 : BDOWN * bw) + 608 (spp == NULL || cpn == NULL || 609 cpn->pos != TBL_CELL_DOWN ? BRIGHT * hw : 0), 1); 610 611 col = tp->tbl.cols; 612 for (;;) { 613 if (cp == NULL) 614 col++; 615 else 616 col = tp->tbl.cols + cp->col; 617 618 /* Print the horizontal line inside this column. */ 619 620 lw = cpp == NULL || cpn == NULL || 621 (cpn->pos != TBL_CELL_DOWN && 622 (dpn == NULL || dpn->string == NULL || 623 strcmp(dpn->string, "\\^") != 0)) 624 ? hw : 0; 625 tbl_direct_border(tp, BHORIZ * lw, 626 col->width + col->spacing / 2); 627 628 /* 629 * Figure out whether a vertical line is crossing 630 * at the end of this column, 631 * and advance to the next column. 632 */ 633 634 uw = dw = 0; 635 if (cpp != NULL) { 636 if (flags != TBL_OPT_DBOX) { 637 uw = cpp->vert; 638 if (uw == 0 && opts & TBL_OPT_ALLBOX) 639 uw = 1; 640 } 641 cpp = cpp->next; 642 } else if (spp != NULL && opts & TBL_OPT_ALLBOX) 643 uw = 1; 644 if (cp != NULL) 645 cp = cp->next; 646 if (cpn != NULL) { 647 if (flags != TBL_OPT_DBOX) { 648 dw = cpn->vert; 649 if (dw == 0 && opts & TBL_OPT_ALLBOX) 650 dw = 1; 651 } 652 cpn = cpn->next; 653 while (dpn != NULL && dpn->layout != cpn) 654 dpn = dpn->next; 655 } else if (spn != NULL && opts & TBL_OPT_ALLBOX) 656 dw = 1; 657 if (col + 1 == tp->tbl.cols + sp->opts->cols) 658 break; 659 660 /* Vertical lines do not cross spanned cells. */ 661 662 if (cpp != NULL && cpp->pos == TBL_CELL_SPAN) 663 uw = 0; 664 if (cpn != NULL && cpn->pos == TBL_CELL_SPAN) 665 dw = 0; 666 667 /* The horizontal line inside the next column. */ 668 669 rw = cpp == NULL || cpn == NULL || 670 (cpn->pos != TBL_CELL_DOWN && 671 (dpn == NULL || dpn->string == NULL || 672 strcmp(dpn->string, "\\^") != 0)) 673 ? hw : 0; 674 675 /* The line crossing at the end of this column. */ 676 677 if (col->spacing) 678 tbl_direct_border(tp, BLEFT * lw + 679 BRIGHT * rw + BUP * uw + BDOWN * dw, 1); 680 681 /* 682 * In ASCII output, a crossing may print two characters. 683 */ 684 685 if (tp->enc != TERMENC_ASCII || (uw < 2 && dw < 2)) 686 uw = dw = 0; 687 if (col->spacing > 2) 688 tbl_direct_border(tp, 689 BHORIZ * rw + BUP * uw + BDOWN * dw, 1); 690 691 /* Padding before the start of the next column. */ 692 693 if (col->spacing > 4) 694 tbl_direct_border(tp, 695 BHORIZ * rw, (col->spacing - 3) / 2); 696 } 697 698 /* Print the right end of the line. */ 699 700 if (flags != 0) { 701 tbl_direct_border(tp, 702 (spp == NULL ? 0 : BUP * bw) + 703 (spn == NULL ? 0 : BDOWN * bw) + 704 (spp == NULL || spn == NULL || 705 spn->layout->last->pos != TBL_CELL_DOWN ? 706 BLEFT * hw : 0), 1); 707 (*tp->endline)(tp); 708 tp->viscol = 0; 709 } 710 } 711 712 static void 713 tbl_data(struct termp *tp, const struct tbl_opts *opts, 714 const struct tbl_cell *cp, const struct tbl_dat *dp, 715 const struct roffcol *col) 716 { 717 switch (cp->pos) { 718 case TBL_CELL_HORIZ: 719 tbl_fill_border(tp, BHORIZ, col->width); 720 return; 721 case TBL_CELL_DHORIZ: 722 tbl_fill_border(tp, BHORIZ * 2, col->width); 723 return; 724 default: 725 break; 726 } 727 728 if (dp == NULL) 729 return; 730 731 switch (dp->pos) { 732 case TBL_DATA_NONE: 733 return; 734 case TBL_DATA_HORIZ: 735 case TBL_DATA_NHORIZ: 736 tbl_fill_border(tp, BHORIZ, col->width); 737 return; 738 case TBL_DATA_NDHORIZ: 739 case TBL_DATA_DHORIZ: 740 tbl_fill_border(tp, BHORIZ * 2, col->width); 741 return; 742 default: 743 break; 744 } 745 746 switch (cp->pos) { 747 case TBL_CELL_LONG: 748 case TBL_CELL_CENTRE: 749 case TBL_CELL_LEFT: 750 case TBL_CELL_RIGHT: 751 tbl_literal(tp, dp, col); 752 break; 753 case TBL_CELL_NUMBER: 754 tbl_number(tp, opts, dp, col); 755 break; 756 case TBL_CELL_DOWN: 757 case TBL_CELL_SPAN: 758 break; 759 default: 760 abort(); 761 } 762 } 763 764 static void 765 tbl_fill_string(struct termp *tp, const char *cp, size_t len) 766 { 767 size_t i, sz; 768 769 sz = term_strlen(tp, cp); 770 for (i = 0; i < len; i += sz) 771 term_word(tp, cp); 772 } 773 774 static void 775 tbl_fill_char(struct termp *tp, char c, size_t len) 776 { 777 char cp[2]; 778 779 cp[0] = c; 780 cp[1] = '\0'; 781 tbl_fill_string(tp, cp, len); 782 } 783 784 static void 785 tbl_fill_border(struct termp *tp, int c, size_t len) 786 { 787 char buf[12]; 788 789 if ((c = borders_locale[c]) > 127) { 790 (void)snprintf(buf, sizeof(buf), "\\[u%04x]", c); 791 tbl_fill_string(tp, buf, len); 792 } else 793 tbl_fill_char(tp, c, len); 794 } 795 796 static void 797 tbl_direct_border(struct termp *tp, int c, size_t len) 798 { 799 size_t i, sz; 800 801 c = borders_locale[c]; 802 sz = (*tp->width)(tp, c); 803 for (i = 0; i < len; i += sz) { 804 (*tp->letter)(tp, c); 805 tp->viscol += sz; 806 } 807 } 808 809 static void 810 tbl_literal(struct termp *tp, const struct tbl_dat *dp, 811 const struct roffcol *col) 812 { 813 size_t len, padl, padr, width; 814 int ic, hspans; 815 816 assert(dp->string); 817 len = term_strlen(tp, dp->string); 818 width = col->width; 819 ic = dp->layout->col; 820 hspans = dp->hspans; 821 while (hspans--) 822 width += tp->tbl.cols[++ic].width + 3; 823 824 padr = width > len ? width - len : 0; 825 padl = 0; 826 827 switch (dp->layout->pos) { 828 case TBL_CELL_LONG: 829 padl = term_len(tp, 1); 830 padr = padr > padl ? padr - padl : 0; 831 break; 832 case TBL_CELL_CENTRE: 833 if (2 > padr) 834 break; 835 padl = padr / 2; 836 padr -= padl; 837 break; 838 case TBL_CELL_RIGHT: 839 padl = padr; 840 padr = 0; 841 break; 842 default: 843 break; 844 } 845 846 tbl_fill_char(tp, ASCII_NBRSP, padl); 847 tbl_word(tp, dp); 848 tbl_fill_char(tp, ASCII_NBRSP, padr); 849 } 850 851 static void 852 tbl_number(struct termp *tp, const struct tbl_opts *opts, 853 const struct tbl_dat *dp, 854 const struct roffcol *col) 855 { 856 const char *cp, *lastdigit, *lastpoint; 857 size_t intsz, padl, totsz; 858 char buf[2]; 859 860 /* 861 * Almost the same code as in tblcalc_number(): 862 * First find the position of the decimal point. 863 */ 864 865 assert(dp->string); 866 lastdigit = lastpoint = NULL; 867 for (cp = dp->string; cp[0] != '\0'; cp++) { 868 if (cp[0] == '\\' && cp[1] == '&') { 869 lastdigit = lastpoint = cp; 870 break; 871 } else if (cp[0] == opts->decimal && 872 (isdigit((unsigned char)cp[1]) || 873 (cp > dp->string && isdigit((unsigned char)cp[-1])))) 874 lastpoint = cp; 875 else if (isdigit((unsigned char)cp[0])) 876 lastdigit = cp; 877 } 878 879 /* Then measure both widths. */ 880 881 padl = 0; 882 totsz = term_strlen(tp, dp->string); 883 if (lastdigit != NULL) { 884 if (lastpoint == NULL) 885 lastpoint = lastdigit + 1; 886 intsz = 0; 887 buf[1] = '\0'; 888 for (cp = dp->string; cp < lastpoint; cp++) { 889 buf[0] = cp[0]; 890 intsz += term_strlen(tp, buf); 891 } 892 893 /* 894 * Pad left to match the decimal position, 895 * but avoid exceeding the total column width. 896 */ 897 898 if (col->decimal > intsz && col->width > totsz) { 899 padl = col->decimal - intsz; 900 if (padl + totsz > col->width) 901 padl = col->width - totsz; 902 } 903 904 /* If it is not a number, simply center the string. */ 905 906 } else if (col->width > totsz) 907 padl = (col->width - totsz) / 2; 908 909 tbl_fill_char(tp, ASCII_NBRSP, padl); 910 tbl_word(tp, dp); 911 912 /* Pad right to fill the column. */ 913 914 if (col->width > padl + totsz) 915 tbl_fill_char(tp, ASCII_NBRSP, col->width - padl - totsz); 916 } 917 918 static void 919 tbl_word(struct termp *tp, const struct tbl_dat *dp) 920 { 921 int prev_font; 922 923 prev_font = tp->fonti; 924 switch (dp->layout->font) { 925 case ESCAPE_FONTBI: 926 term_fontpush(tp, TERMFONT_BI); 927 break; 928 case ESCAPE_FONTBOLD: 929 case ESCAPE_FONTCB: 930 term_fontpush(tp, TERMFONT_BOLD); 931 break; 932 case ESCAPE_FONTITALIC: 933 case ESCAPE_FONTCI: 934 term_fontpush(tp, TERMFONT_UNDER); 935 break; 936 case ESCAPE_FONTROMAN: 937 case ESCAPE_FONTCR: 938 break; 939 default: 940 abort(); 941 } 942 943 term_word(tp, dp->string); 944 945 term_fontpopq(tp, prev_font); 946 } 947