1 /* $OpenBSD: tbl_term.c,v 1.66 2022/08/28 10:57:52 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2011-2022 Ingo Schwarze <schwarze@openbsd.org> 4 * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 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 tp->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE); 292 tbl_data(tp, sp->opts, cp, dp, tp->tbl.cols + ic); 293 if (dp != NULL && 294 (ic || sp->layout->first->pos != TBL_CELL_SPAN)) { 295 hspans = dp->hspans; 296 dp = dp->next; 297 } 298 } 299 break; 300 } 301 302 do { 303 /* Print the vertical frame at the start of each row. */ 304 305 tp->tcol = tp->tcols; 306 uvert = dvert = sp->opts->opts & TBL_OPT_DBOX ? 2 : 307 sp->opts->opts & TBL_OPT_BOX ? 1 : 0; 308 if (sp->pos == TBL_SPAN_DATA && uvert < sp->layout->vert) 309 uvert = dvert = sp->layout->vert; 310 if (sp->next != NULL && sp->next->pos == TBL_SPAN_DATA && 311 dvert < sp->next->layout->vert) 312 dvert = sp->next->layout->vert; 313 if (sp->prev != NULL && uvert < sp->prev->layout->vert && 314 (horiz || (IS_HORIZ(sp->layout->first) && 315 !IS_HORIZ(sp->prev->layout->first)))) 316 uvert = sp->prev->layout->vert; 317 rhori = sp->pos == TBL_SPAN_DHORIZ || 318 (sp->first != NULL && sp->first->pos == TBL_DATA_DHORIZ) || 319 sp->layout->first->pos == TBL_CELL_DHORIZ ? 2 : 320 sp->pos == TBL_SPAN_HORIZ || 321 (sp->first != NULL && sp->first->pos == TBL_DATA_HORIZ) || 322 sp->layout->first->pos == TBL_CELL_HORIZ ? 1 : 0; 323 fc = BUP * uvert + BDOWN * dvert + BRIGHT * rhori; 324 if (uvert > 0 || dvert > 0 || (horiz && sp->opts->lvert)) { 325 (*tp->advance)(tp, tp->tcols->offset); 326 tp->viscol = tp->tcol->offset; 327 tbl_direct_border(tp, fc, 1); 328 } 329 330 /* Print the data cells. */ 331 332 more = 0; 333 if (horiz) 334 tbl_hrule(tp, sp->prev, sp, sp->next, 0); 335 else { 336 cp = sp->layout->first; 337 cpn = sp->next == NULL ? NULL : 338 sp->next->layout->first; 339 cpp = sp->prev == NULL ? NULL : 340 sp->prev->layout->first; 341 dp = sp->first; 342 hspans = 0; 343 for (ic = 0; ic < sp->opts->cols; ic++) { 344 345 /* 346 * Figure out whether to print a 347 * vertical line after this cell 348 * and advance to next layout cell. 349 */ 350 351 uvert = dvert = fc = 0; 352 if (cp != NULL) { 353 cps = cp; 354 while (cps->next != NULL && 355 cps->next->pos == TBL_CELL_SPAN) 356 cps = cps->next; 357 if (sp->pos == TBL_SPAN_DATA) 358 uvert = dvert = cps->vert; 359 switch (cp->pos) { 360 case TBL_CELL_HORIZ: 361 fc = BHORIZ; 362 break; 363 case TBL_CELL_DHORIZ: 364 fc = BHORIZ * 2; 365 break; 366 default: 367 break; 368 } 369 } 370 if (cpp != NULL) { 371 if (uvert < cpp->vert && 372 cp != NULL && 373 ((IS_HORIZ(cp) && 374 !IS_HORIZ(cpp)) || 375 (cp->next != NULL && 376 cpp->next != NULL && 377 IS_HORIZ(cp->next) && 378 !IS_HORIZ(cpp->next)))) 379 uvert = cpp->vert; 380 cpp = cpp->next; 381 } 382 if (sp->opts->opts & TBL_OPT_ALLBOX) { 383 if (uvert == 0) 384 uvert = 1; 385 if (dvert == 0) 386 dvert = 1; 387 } 388 if (cpn != NULL) { 389 if (dvert == 0 || 390 (dvert < cpn->vert && 391 tp->enc == TERMENC_UTF8)) 392 dvert = cpn->vert; 393 cpn = cpn->next; 394 } 395 396 lhori = (cp != NULL && 397 cp->pos == TBL_CELL_DHORIZ) || 398 (dp != NULL && 399 dp->pos == TBL_DATA_DHORIZ) ? 2 : 400 (cp != NULL && 401 cp->pos == TBL_CELL_HORIZ) || 402 (dp != NULL && 403 dp->pos == TBL_DATA_HORIZ) ? 1 : 0; 404 405 /* 406 * Skip later cells in a span, 407 * figure out whether to start a span, 408 * and advance to next data cell. 409 */ 410 411 if (hspans) { 412 hspans--; 413 cp = cp->next; 414 continue; 415 } 416 if (dp != NULL && (ic || 417 sp->layout->first->pos != TBL_CELL_SPAN)) { 418 hspans = dp->hspans; 419 dp = dp->next; 420 } 421 422 /* 423 * Print one line of text in the cell 424 * and remember whether there is more. 425 */ 426 427 tp->tcol++; 428 if (tp->tcol->col < tp->tcol->lastcol) 429 term_flushln(tp); 430 if (tp->tcol->col < tp->tcol->lastcol) 431 more = 1; 432 433 /* 434 * Vertical frames between data cells, 435 * but not after the last column. 436 */ 437 438 if (fc == 0 && 439 ((uvert == 0 && dvert == 0 && 440 cp != NULL && (cp->next == NULL || 441 !IS_HORIZ(cp->next))) || 442 tp->tcol + 1 == 443 tp->tcols + tp->lasttcol)) { 444 if (cp != NULL) 445 cp = cp->next; 446 continue; 447 } 448 449 if (tp->viscol < tp->tcol->rmargin) { 450 (*tp->advance)(tp, tp->tcol->rmargin 451 - tp->viscol); 452 tp->viscol = tp->tcol->rmargin; 453 } 454 while (tp->viscol < tp->tcol->rmargin + 455 tp->tbl.cols[ic].spacing / 2) 456 tbl_direct_border(tp, 457 BHORIZ * lhori, 1); 458 459 if (tp->tcol + 1 == tp->tcols + tp->lasttcol) 460 continue; 461 462 if (cp != NULL) 463 cp = cp->next; 464 465 rhori = (cp != NULL && 466 cp->pos == TBL_CELL_DHORIZ) || 467 (dp != NULL && 468 dp->pos == TBL_DATA_DHORIZ) ? 2 : 469 (cp != NULL && 470 cp->pos == TBL_CELL_HORIZ) || 471 (dp != NULL && 472 dp->pos == TBL_DATA_HORIZ) ? 1 : 0; 473 474 if (tp->tbl.cols[ic].spacing) 475 tbl_direct_border(tp, 476 BLEFT * lhori + BRIGHT * rhori + 477 BUP * uvert + BDOWN * dvert, 1); 478 479 if (tp->enc == TERMENC_UTF8) 480 uvert = dvert = 0; 481 482 if (tp->tbl.cols[ic].spacing > 2 && 483 (uvert > 1 || dvert > 1 || rhori)) 484 tbl_direct_border(tp, 485 BHORIZ * rhori + 486 BUP * (uvert > 1) + 487 BDOWN * (dvert > 1), 1); 488 } 489 } 490 491 /* Print the vertical frame at the end of each row. */ 492 493 uvert = dvert = sp->opts->opts & TBL_OPT_DBOX ? 2 : 494 sp->opts->opts & TBL_OPT_BOX ? 1 : 0; 495 if (sp->pos == TBL_SPAN_DATA && 496 uvert < sp->layout->last->vert && 497 sp->layout->last->col + 1 == sp->opts->cols) 498 uvert = dvert = sp->layout->last->vert; 499 if (sp->next != NULL && 500 dvert < sp->next->layout->last->vert && 501 sp->next->layout->last->col + 1 == sp->opts->cols) 502 dvert = sp->next->layout->last->vert; 503 if (sp->prev != NULL && 504 uvert < sp->prev->layout->last->vert && 505 sp->prev->layout->last->col + 1 == sp->opts->cols && 506 (horiz || (IS_HORIZ(sp->layout->last) && 507 !IS_HORIZ(sp->prev->layout->last)))) 508 uvert = sp->prev->layout->last->vert; 509 lhori = sp->pos == TBL_SPAN_DHORIZ || 510 (sp->last != NULL && 511 sp->last->pos == TBL_DATA_DHORIZ && 512 sp->last->layout->col + 1 == sp->opts->cols) || 513 (sp->layout->last->pos == TBL_CELL_DHORIZ && 514 sp->layout->last->col + 1 == sp->opts->cols) ? 2 : 515 sp->pos == TBL_SPAN_HORIZ || 516 (sp->last != NULL && 517 sp->last->pos == TBL_DATA_HORIZ && 518 sp->last->layout->col + 1 == sp->opts->cols) || 519 (sp->layout->last->pos == TBL_CELL_HORIZ && 520 sp->layout->last->col + 1 == sp->opts->cols) ? 1 : 0; 521 fc = BUP * uvert + BDOWN * dvert + BLEFT * lhori; 522 if (uvert > 0 || dvert > 0 || (horiz && sp->opts->rvert)) { 523 if (horiz == 0 && (IS_HORIZ(sp->layout->last) == 0 || 524 sp->layout->last->col + 1 < sp->opts->cols)) { 525 tp->tcol++; 526 do { 527 tbl_direct_border(tp, 528 BHORIZ * lhori, 1); 529 } while (tp->viscol < tp->tcol->offset); 530 } 531 tbl_direct_border(tp, fc, 1); 532 } 533 (*tp->endline)(tp); 534 tp->viscol = 0; 535 } while (more); 536 537 /* 538 * Clean up after this row. If it is the last line 539 * of the table, print the box line and clean up 540 * column data; otherwise, print the allbox line. 541 */ 542 543 term_setcol(tp, 1); 544 tp->flags &= ~TERMP_MULTICOL; 545 tp->tcol->rmargin = tp->maxrmargin; 546 if (sp->next == NULL) { 547 if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) 548 tbl_hrule(tp, sp, sp, NULL, TBL_OPT_BOX); 549 if (tp->enc == TERMENC_ASCII && 550 sp->opts->opts & TBL_OPT_DBOX) 551 tbl_hrule(tp, sp, sp, NULL, TBL_OPT_DBOX); 552 assert(tp->tbl.cols); 553 free(tp->tbl.cols); 554 tp->tbl.cols = NULL; 555 } else if (horiz == 0 && sp->opts->opts & TBL_OPT_ALLBOX && 556 (sp->next == NULL || sp->next->pos == TBL_SPAN_DATA || 557 sp->next->next != NULL)) 558 tbl_hrule(tp, sp, sp, sp->next, TBL_OPT_ALLBOX); 559 560 tp->tcol->offset = save_offset; 561 tp->flags &= ~TERMP_NONOSPACE; 562 } 563 564 static void 565 tbl_hrule(struct termp *tp, const struct tbl_span *spp, 566 const struct tbl_span *sp, const struct tbl_span *spn, int flags) 567 { 568 const struct tbl_cell *cpp; /* Layout cell above this line. */ 569 const struct tbl_cell *cp; /* Layout cell in this line. */ 570 const struct tbl_cell *cpn; /* Layout cell below this line. */ 571 const struct tbl_dat *dpn; /* Data cell below this line. */ 572 const struct roffcol *col; /* Contains width and spacing. */ 573 int opts; /* For the table as a whole. */ 574 int bw; /* Box line width. */ 575 int hw; /* Horizontal line width. */ 576 int lw, rw; /* Left and right line widths. */ 577 int uw, dw; /* Vertical line widths. */ 578 579 cpp = spp == NULL ? NULL : spp->layout->first; 580 cp = sp == NULL ? NULL : sp->layout->first; 581 cpn = spn == NULL ? NULL : spn->layout->first; 582 dpn = NULL; 583 if (spn != NULL) { 584 if (spn->pos == TBL_SPAN_DATA) 585 dpn = spn->first; 586 else if (spn->next != NULL) 587 dpn = spn->next->first; 588 } 589 opts = sp->opts->opts; 590 bw = opts & TBL_OPT_DBOX ? (tp->enc == TERMENC_UTF8 ? 2 : 1) : 591 opts & (TBL_OPT_BOX | TBL_OPT_ALLBOX) ? 1 : 0; 592 hw = flags == TBL_OPT_DBOX || flags == TBL_OPT_BOX ? bw : 593 sp->pos == TBL_SPAN_DHORIZ ? 2 : 1; 594 595 /* Print the left end of the line. */ 596 597 if (tp->viscol == 0) { 598 (*tp->advance)(tp, tp->tcols->offset); 599 tp->viscol = tp->tcols->offset; 600 } 601 if (flags != 0) 602 tbl_direct_border(tp, 603 (spp == NULL ? 0 : BUP * bw) + 604 (spn == NULL ? 0 : BDOWN * bw) + 605 (spp == NULL || cpn == NULL || 606 cpn->pos != TBL_CELL_DOWN ? BRIGHT * hw : 0), 1); 607 608 col = tp->tbl.cols; 609 for (;;) { 610 if (cp == NULL) 611 col++; 612 else 613 col = tp->tbl.cols + cp->col; 614 615 /* Print the horizontal line inside this column. */ 616 617 lw = cpp == NULL || cpn == NULL || 618 (cpn->pos != TBL_CELL_DOWN && 619 (dpn == NULL || dpn->string == NULL || 620 strcmp(dpn->string, "\\^") != 0)) 621 ? hw : 0; 622 tbl_direct_border(tp, BHORIZ * lw, 623 col->width + col->spacing / 2); 624 625 /* 626 * Figure out whether a vertical line is crossing 627 * at the end of this column, 628 * and advance to the next column. 629 */ 630 631 uw = dw = 0; 632 if (cpp != NULL) { 633 if (flags != TBL_OPT_DBOX) { 634 uw = cpp->vert; 635 if (uw == 0 && opts & TBL_OPT_ALLBOX) 636 uw = 1; 637 } 638 cpp = cpp->next; 639 } else if (spp != NULL && opts & TBL_OPT_ALLBOX) 640 uw = 1; 641 if (cp != NULL) 642 cp = cp->next; 643 if (cpn != NULL) { 644 if (flags != TBL_OPT_DBOX) { 645 dw = cpn->vert; 646 if (dw == 0 && opts & TBL_OPT_ALLBOX) 647 dw = 1; 648 } 649 cpn = cpn->next; 650 while (dpn != NULL && dpn->layout != cpn) 651 dpn = dpn->next; 652 } else if (spn != NULL && opts & TBL_OPT_ALLBOX) 653 dw = 1; 654 if (col + 1 == tp->tbl.cols + sp->opts->cols) 655 break; 656 657 /* Vertical lines do not cross spanned cells. */ 658 659 if (cpp != NULL && cpp->pos == TBL_CELL_SPAN) 660 uw = 0; 661 if (cpn != NULL && cpn->pos == TBL_CELL_SPAN) 662 dw = 0; 663 664 /* The horizontal line inside the next column. */ 665 666 rw = cpp == NULL || cpn == NULL || 667 (cpn->pos != TBL_CELL_DOWN && 668 (dpn == NULL || dpn->string == NULL || 669 strcmp(dpn->string, "\\^") != 0)) 670 ? hw : 0; 671 672 /* The line crossing at the end of this column. */ 673 674 if (col->spacing) 675 tbl_direct_border(tp, BLEFT * lw + 676 BRIGHT * rw + BUP * uw + BDOWN * dw, 1); 677 678 /* 679 * In ASCII output, a crossing may print two characters. 680 */ 681 682 if (tp->enc != TERMENC_ASCII || (uw < 2 && dw < 2)) 683 uw = dw = 0; 684 if (col->spacing > 2) 685 tbl_direct_border(tp, 686 BHORIZ * rw + BUP * uw + BDOWN * dw, 1); 687 688 /* Padding before the start of the next column. */ 689 690 if (col->spacing > 4) 691 tbl_direct_border(tp, 692 BHORIZ * rw, (col->spacing - 3) / 2); 693 } 694 695 /* Print the right end of the line. */ 696 697 if (flags != 0) { 698 tbl_direct_border(tp, 699 (spp == NULL ? 0 : BUP * bw) + 700 (spn == NULL ? 0 : BDOWN * bw) + 701 (spp == NULL || spn == NULL || 702 spn->layout->last->pos != TBL_CELL_DOWN ? 703 BLEFT * hw : 0), 1); 704 (*tp->endline)(tp); 705 tp->viscol = 0; 706 } 707 } 708 709 static void 710 tbl_data(struct termp *tp, const struct tbl_opts *opts, 711 const struct tbl_cell *cp, const struct tbl_dat *dp, 712 const struct roffcol *col) 713 { 714 switch (cp->pos) { 715 case TBL_CELL_HORIZ: 716 tbl_fill_border(tp, BHORIZ, col->width); 717 return; 718 case TBL_CELL_DHORIZ: 719 tbl_fill_border(tp, BHORIZ * 2, col->width); 720 return; 721 default: 722 break; 723 } 724 725 if (dp == NULL) 726 return; 727 728 switch (dp->pos) { 729 case TBL_DATA_NONE: 730 return; 731 case TBL_DATA_HORIZ: 732 case TBL_DATA_NHORIZ: 733 tbl_fill_border(tp, BHORIZ, col->width); 734 return; 735 case TBL_DATA_NDHORIZ: 736 case TBL_DATA_DHORIZ: 737 tbl_fill_border(tp, BHORIZ * 2, col->width); 738 return; 739 default: 740 break; 741 } 742 743 switch (cp->pos) { 744 case TBL_CELL_LONG: 745 case TBL_CELL_CENTRE: 746 case TBL_CELL_LEFT: 747 case TBL_CELL_RIGHT: 748 tbl_literal(tp, dp, col); 749 break; 750 case TBL_CELL_NUMBER: 751 tbl_number(tp, opts, dp, col); 752 break; 753 case TBL_CELL_DOWN: 754 case TBL_CELL_SPAN: 755 break; 756 default: 757 abort(); 758 } 759 } 760 761 static void 762 tbl_fill_string(struct termp *tp, const char *cp, size_t len) 763 { 764 size_t i, sz; 765 766 sz = term_strlen(tp, cp); 767 for (i = 0; i < len; i += sz) 768 term_word(tp, cp); 769 } 770 771 static void 772 tbl_fill_char(struct termp *tp, char c, size_t len) 773 { 774 char cp[2]; 775 776 cp[0] = c; 777 cp[1] = '\0'; 778 tbl_fill_string(tp, cp, len); 779 } 780 781 static void 782 tbl_fill_border(struct termp *tp, int c, size_t len) 783 { 784 char buf[12]; 785 786 if ((c = borders_locale[c]) > 127) { 787 (void)snprintf(buf, sizeof(buf), "\\[u%04x]", c); 788 tbl_fill_string(tp, buf, len); 789 } else 790 tbl_fill_char(tp, c, len); 791 } 792 793 static void 794 tbl_direct_border(struct termp *tp, int c, size_t len) 795 { 796 size_t i, sz; 797 798 c = borders_locale[c]; 799 sz = (*tp->width)(tp, c); 800 for (i = 0; i < len; i += sz) { 801 (*tp->letter)(tp, c); 802 tp->viscol += sz; 803 } 804 } 805 806 static void 807 tbl_literal(struct termp *tp, const struct tbl_dat *dp, 808 const struct roffcol *col) 809 { 810 size_t len, padl, padr, width; 811 int ic, hspans; 812 813 assert(dp->string); 814 len = term_strlen(tp, dp->string); 815 width = col->width; 816 ic = dp->layout->col; 817 hspans = dp->hspans; 818 while (hspans--) { 819 width += tp->tbl.cols[ic].spacing; 820 ic++; 821 width += tp->tbl.cols[ic].width; 822 } 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