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