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