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