Annotation of src/usr.bin/mandoc/tbl_term.c, Revision 1.51
1.51 ! schwarze 1: /* $OpenBSD: tbl_term.c,v 1.50 2018/11/28 04:47:46 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.51 ! schwarze 438: if (fc == 0 &&
! 439: ((uvert == 0 && dvert == 0 &&
! 440: cp != NULL && (cp->next == NULL ||
1.50 schwarze 441: !IS_HORIZ(cp->next))) ||
1.51 ! schwarze 442: tp->tcol + 1 ==
! 443: tp->tcols + tp->lasttcol)) {
! 444: if (cp != NULL)
! 445: cp = cp->next;
1.41 schwarze 446: continue;
1.50 schwarze 447: }
1.41 schwarze 448:
1.43 schwarze 449: if (tp->viscol < tp->tcol->rmargin) {
1.41 schwarze 450: (*tp->advance)(tp, tp->tcol->rmargin
451: - tp->viscol);
452: tp->viscol = tp->tcol->rmargin;
453: }
1.43 schwarze 454: while (tp->viscol < tp->tcol->rmargin +
1.50 schwarze 455: tp->tbl.cols[ic].spacing / 2)
456: tbl_direct_border(tp, fc, 1);
1.41 schwarze 457:
1.39 schwarze 458: if (tp->tcol + 1 == tp->tcols + tp->lasttcol)
459: continue;
1.35 schwarze 460:
1.50 schwarze 461: if (cp != NULL) {
1.41 schwarze 462: switch (cp->pos) {
463: case TBL_CELL_HORIZ:
1.50 schwarze 464: fc = BLEFT;
1.41 schwarze 465: break;
466: case TBL_CELL_DHORIZ:
1.50 schwarze 467: fc = BLEFT * 2;
1.41 schwarze 468: break;
469: default:
1.50 schwarze 470: fc = 0;
1.41 schwarze 471: break;
472: }
1.50 schwarze 473: cp = cp->next;
1.41 schwarze 474: }
1.50 schwarze 475: if (cp != NULL) {
476: switch (cp->pos) {
477: case TBL_CELL_HORIZ:
478: fc += BRIGHT;
479: break;
480: case TBL_CELL_DHORIZ:
481: fc += BRIGHT * 2;
482: break;
483: default:
484: break;
485: }
1.43 schwarze 486: }
1.50 schwarze 487: if (tp->tbl.cols[ic].spacing)
488: tbl_direct_border(tp, fc +
489: BUP * uvert + BDOWN * dvert, 1);
490:
491: if (tp->enc == TERMENC_UTF8)
492: uvert = dvert = 0;
1.41 schwarze 493:
1.50 schwarze 494: if (fc != 0) {
1.41 schwarze 495: if (cp != NULL &&
496: cp->pos == TBL_CELL_HORIZ)
1.50 schwarze 497: fc = BHORIZ;
1.41 schwarze 498: else if (cp != NULL &&
499: cp->pos == TBL_CELL_DHORIZ)
1.50 schwarze 500: fc = BHORIZ * 2;
1.41 schwarze 501: else
1.50 schwarze 502: fc = 0;
1.35 schwarze 503: }
1.43 schwarze 504: if (tp->tbl.cols[ic].spacing > 2 &&
1.50 schwarze 505: (uvert > 1 || dvert > 1 || fc != 0))
506: tbl_direct_border(tp, fc +
507: BUP * (uvert > 1) +
508: BDOWN * (dvert > 1), 1);
1.35 schwarze 509: }
510: }
1.5 schwarze 511:
1.35 schwarze 512: /* Print the vertical frame at the end of each row. */
1.7 schwarze 513:
1.50 schwarze 514: uvert = dvert = sp->opts->opts & TBL_OPT_DBOX ? 2 :
515: sp->opts->opts & TBL_OPT_BOX ? 1 : 0;
516: if (sp->pos == TBL_SPAN_DATA &&
517: uvert < sp->layout->last->vert &&
518: sp->layout->last->col + 1 == sp->opts->cols)
519: uvert = dvert = sp->layout->last->vert;
520: if (sp->next != NULL &&
521: dvert < sp->next->layout->last->vert &&
522: sp->next->layout->last->col + 1 == sp->opts->cols)
523: dvert = sp->next->layout->last->vert;
524: if (sp->prev != NULL &&
525: uvert < sp->prev->layout->last->vert &&
526: sp->prev->layout->last->col + 1 == sp->opts->cols &&
527: (horiz || (IS_HORIZ(sp->layout->last) &&
528: !IS_HORIZ(sp->prev->layout->last))))
529: uvert = sp->prev->layout->last->vert;
530: line = sp->pos == TBL_SPAN_DHORIZ ||
531: (sp->layout->last->pos == TBL_CELL_DHORIZ &&
532: sp->layout->last->col + 1 == sp->opts->cols) ? 2 :
533: sp->pos == TBL_SPAN_HORIZ ||
534: (sp->layout->last->pos == TBL_CELL_HORIZ &&
535: sp->layout->last->col + 1 == sp->opts->cols) ? 1 : 0;
536: fc = BUP * uvert + BDOWN * dvert + BLEFT * line;
537: if (uvert > 0 || dvert > 0 || (horiz && sp->opts->rvert)) {
1.41 schwarze 538: if (horiz == 0 && (IS_HORIZ(sp->layout->last) == 0 ||
539: sp->layout->last->col + 1 < sp->opts->cols)) {
1.35 schwarze 540: tp->tcol++;
541: (*tp->advance)(tp,
542: tp->tcol->offset > tp->viscol ?
543: tp->tcol->offset - tp->viscol : 1);
544: }
1.50 schwarze 545: tbl_direct_border(tp, fc, 1);
1.35 schwarze 546: }
547: (*tp->endline)(tp);
548: tp->viscol = 0;
549: } while (more);
1.1 schwarze 550:
1.5 schwarze 551: /*
1.41 schwarze 552: * Clean up after this row. If it is the last line
553: * of the table, print the box line and clean up
554: * column data; otherwise, print the allbox line.
1.5 schwarze 555: */
1.1 schwarze 556:
1.35 schwarze 557: term_setcol(tp, 1);
558: tp->flags &= ~TERMP_MULTICOL;
559: tp->tcol->rmargin = tp->maxrmargin;
1.25 schwarze 560: if (sp->next == NULL) {
1.21 schwarze 561: if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) {
1.50 schwarze 562: tbl_hrule(tp, sp, HRULE_UP | HRULE_ENDS);
1.13 schwarze 563: tp->skipvsp = 1;
564: }
1.50 schwarze 565: if (tp->enc == TERMENC_ASCII &&
566: sp->opts->opts & TBL_OPT_DBOX) {
567: tbl_hrule(tp, sp, HRULE_DBOX | HRULE_ENDS);
1.13 schwarze 568: tp->skipvsp = 2;
569: }
1.6 schwarze 570: assert(tp->tbl.cols);
571: free(tp->tbl.cols);
572: tp->tbl.cols = NULL;
1.33 schwarze 573: tp->tcol->offset = offset;
1.38 schwarze 574: } else if (horiz == 0 && sp->opts->opts & TBL_OPT_ALLBOX &&
575: (sp->next == NULL || sp->next->pos == TBL_SPAN_DATA ||
576: sp->next->next != NULL))
1.50 schwarze 577: tbl_hrule(tp, sp,
578: HRULE_DATA | HRULE_DOWN | HRULE_UP | HRULE_ENDS);
1.37 schwarze 579:
1.35 schwarze 580: tp->flags &= ~TERMP_NONOSPACE;
1.1 schwarze 581: }
582:
583: static void
1.50 schwarze 584: tbl_hrule(struct termp *tp, const struct tbl_span *sp, int flags)
1.1 schwarze 585: {
1.41 schwarze 586: const struct tbl_cell *cp, *cpn, *cpp;
1.43 schwarze 587: const struct roffcol *col;
1.50 schwarze 588: int cross, dvert, line, linewidth, uvert;
1.21 schwarze 589:
1.41 schwarze 590: cp = sp->layout->first;
1.50 schwarze 591: cpn = cpp = NULL;
592: if (flags & HRULE_DATA) {
593: linewidth = sp->pos == TBL_SPAN_DHORIZ ? 2 : 1;
594: cpn = sp->next == NULL ? NULL : sp->next->layout->first;
595: if (cpn == cp)
596: cpn = NULL;
597: } else
598: linewidth = tp->enc == TERMENC_UTF8 &&
599: sp->opts->opts & TBL_OPT_DBOX ? 2 : 1;
600: if (tp->viscol == 0) {
601: (*tp->advance)(tp, tp->tcols->offset);
602: tp->viscol = tp->tcols->offset;
603: }
604: if (flags & HRULE_ENDS)
605: tbl_direct_border(tp, linewidth * (BRIGHT +
606: (flags & (HRULE_UP | HRULE_DBOX) ? BUP : 0) +
607: (flags & (HRULE_DOWN | HRULE_DBOX) ? BDOWN : 0)), 1);
608: else {
609: cpp = sp->prev == NULL ? NULL : sp->prev->layout->first;
610: if (cpp == cp)
611: cpp = NULL;
612: }
1.21 schwarze 613: for (;;) {
1.43 schwarze 614: col = tp->tbl.cols + cp->col;
1.50 schwarze 615: line = cpn == NULL || cpn->pos != TBL_CELL_DOWN ?
616: BHORIZ * linewidth : 0;
617: tbl_direct_border(tp, line, col->width + col->spacing / 2);
618: uvert = dvert = 0;
619: if (flags & HRULE_UP &&
620: (tp->enc == TERMENC_ASCII || sp->pos == TBL_SPAN_DATA ||
621: (sp->prev != NULL && sp->prev->layout == sp->layout)))
622: uvert = cp->vert;
623: if (flags & HRULE_DOWN)
624: dvert = cp->vert;
1.41 schwarze 625: if ((cp = cp->next) == NULL)
1.50 schwarze 626: break;
1.41 schwarze 627: if (cpp != NULL) {
1.50 schwarze 628: if (uvert < cpp->vert)
629: uvert = cpp->vert;
1.41 schwarze 630: cpp = cpp->next;
631: }
632: if (cpn != NULL) {
1.50 schwarze 633: if (dvert < cpn->vert)
634: dvert = cpn->vert;
1.41 schwarze 635: cpn = cpn->next;
1.21 schwarze 636: }
1.50 schwarze 637: if (sp->opts->opts & TBL_OPT_ALLBOX) {
638: if (flags & HRULE_UP && uvert == 0)
639: uvert = 1;
640: if (flags & HRULE_DOWN && dvert == 0)
641: dvert = 1;
642: }
643: cross = BHORIZ * linewidth + BUP * uvert + BDOWN * dvert;
1.43 schwarze 644: if (col->spacing)
1.50 schwarze 645: tbl_direct_border(tp, cross, 1);
1.43 schwarze 646: if (col->spacing > 2)
1.50 schwarze 647: tbl_direct_border(tp, tp->enc == TERMENC_ASCII &&
648: (uvert > 1 || dvert > 1) ? cross : line, 1);
1.43 schwarze 649: if (col->spacing > 4)
1.50 schwarze 650: tbl_direct_border(tp, line, (col->spacing - 3) / 2);
1.11 schwarze 651: }
1.50 schwarze 652: if (flags & HRULE_ENDS) {
653: tbl_direct_border(tp, linewidth * (BLEFT +
654: (flags & (HRULE_UP | HRULE_DBOX) ? BUP : 0) +
655: (flags & (HRULE_DOWN | HRULE_DBOX) ? BDOWN : 0)), 1);
656: (*tp->endline)(tp);
657: tp->viscol = 0;
1.11 schwarze 658: }
1.1 schwarze 659: }
660:
661: static void
1.14 schwarze 662: tbl_data(struct termp *tp, const struct tbl_opts *opts,
1.41 schwarze 663: const struct tbl_cell *cp, const struct tbl_dat *dp,
664: const struct roffcol *col)
1.1 schwarze 665: {
1.41 schwarze 666: switch (cp->pos) {
667: case TBL_CELL_HORIZ:
1.50 schwarze 668: tbl_fill_border(tp, BHORIZ, col->width);
1.41 schwarze 669: return;
670: case TBL_CELL_DHORIZ:
1.50 schwarze 671: tbl_fill_border(tp, BHORIZ * 2, col->width);
1.41 schwarze 672: return;
673: default:
674: break;
675: }
1.1 schwarze 676:
1.44 schwarze 677: if (dp == NULL)
1.1 schwarze 678: return;
679:
1.5 schwarze 680: switch (dp->pos) {
1.16 schwarze 681: case TBL_DATA_NONE:
1.5 schwarze 682: return;
1.16 schwarze 683: case TBL_DATA_HORIZ:
684: case TBL_DATA_NHORIZ:
1.50 schwarze 685: tbl_fill_border(tp, BHORIZ, col->width);
1.5 schwarze 686: return;
1.16 schwarze 687: case TBL_DATA_NDHORIZ:
688: case TBL_DATA_DHORIZ:
1.50 schwarze 689: tbl_fill_border(tp, BHORIZ * 2, col->width);
1.5 schwarze 690: return;
1.1 schwarze 691: default:
692: break;
693: }
1.16 schwarze 694:
1.41 schwarze 695: switch (cp->pos) {
1.16 schwarze 696: case TBL_CELL_LONG:
697: case TBL_CELL_CENTRE:
698: case TBL_CELL_LEFT:
699: case TBL_CELL_RIGHT:
1.6 schwarze 700: tbl_literal(tp, dp, col);
1.1 schwarze 701: break;
1.16 schwarze 702: case TBL_CELL_NUMBER:
1.14 schwarze 703: tbl_number(tp, opts, dp, col);
1.4 schwarze 704: break;
1.16 schwarze 705: case TBL_CELL_DOWN:
1.44 schwarze 706: case TBL_CELL_SPAN:
1.7 schwarze 707: break;
1.1 schwarze 708: default:
709: abort();
710: }
711: }
712:
713: static void
1.50 schwarze 714: tbl_fill_string(struct termp *tp, const char *cp, size_t len)
715: {
716: size_t i, sz;
717:
718: sz = term_strlen(tp, cp);
719: for (i = 0; i < len; i += sz)
720: term_word(tp, cp);
721: }
722:
723: static void
724: tbl_fill_char(struct termp *tp, char c, size_t len)
1.5 schwarze 725: {
1.50 schwarze 726: char cp[2];
1.1 schwarze 727:
1.5 schwarze 728: cp[0] = c;
729: cp[1] = '\0';
1.50 schwarze 730: tbl_fill_string(tp, cp, len);
731: }
1.1 schwarze 732:
1.50 schwarze 733: static void
734: tbl_fill_border(struct termp *tp, int c, size_t len)
735: {
736: char buf[12];
737:
738: if ((c = borders_locale[c]) > 127) {
739: (void)snprintf(buf, sizeof(buf), "\\[u%04x]", c);
740: tbl_fill_string(tp, buf, len);
741: } else
742: tbl_fill_char(tp, c, len);
743: }
744:
745: static void
746: tbl_direct_border(struct termp *tp, int c, size_t len)
747: {
748: size_t i, sz;
1.1 schwarze 749:
1.50 schwarze 750: c = borders_locale[c];
751: sz = (*tp->width)(tp, c);
752: for (i = 0; i < len; i += sz) {
753: (*tp->letter)(tp, c);
754: tp->viscol += sz;
755: }
1.1 schwarze 756: }
757:
758: static void
1.16 schwarze 759: tbl_literal(struct termp *tp, const struct tbl_dat *dp,
1.6 schwarze 760: const struct roffcol *col)
1.1 schwarze 761: {
1.24 schwarze 762: size_t len, padl, padr, width;
1.49 schwarze 763: int ic, hspans;
1.1 schwarze 764:
1.7 schwarze 765: assert(dp->string);
1.10 schwarze 766: len = term_strlen(tp, dp->string);
1.12 schwarze 767: width = col->width;
1.24 schwarze 768: ic = dp->layout->col;
1.49 schwarze 769: hspans = dp->hspans;
770: while (hspans--)
1.24 schwarze 771: width += tp->tbl.cols[++ic].width + 3;
1.12 schwarze 772:
773: padr = width > len ? width - len : 0;
1.10 schwarze 774: padl = 0;
1.5 schwarze 775:
1.7 schwarze 776: switch (dp->layout->pos) {
1.16 schwarze 777: case TBL_CELL_LONG:
1.10 schwarze 778: padl = term_len(tp, 1);
779: padr = padr > padl ? padr - padl : 0;
1.1 schwarze 780: break;
1.16 schwarze 781: case TBL_CELL_CENTRE:
1.10 schwarze 782: if (2 > padr)
1.8 schwarze 783: break;
1.10 schwarze 784: padl = padr / 2;
1.8 schwarze 785: padr -= padl;
1.1 schwarze 786: break;
1.16 schwarze 787: case TBL_CELL_RIGHT:
1.10 schwarze 788: padl = padr;
789: padr = 0;
1.1 schwarze 790: break;
791: default:
792: break;
793: }
794:
1.50 schwarze 795: tbl_fill_char(tp, ASCII_NBRSP, padl);
1.17 schwarze 796: tbl_word(tp, dp);
1.50 schwarze 797: tbl_fill_char(tp, ASCII_NBRSP, padr);
1.1 schwarze 798: }
799:
1.5 schwarze 800: static void
1.14 schwarze 801: tbl_number(struct termp *tp, const struct tbl_opts *opts,
1.5 schwarze 802: const struct tbl_dat *dp,
1.6 schwarze 803: const struct roffcol *col)
1.5 schwarze 804: {
1.48 schwarze 805: const char *cp, *lastdigit, *lastpoint;
806: size_t intsz, padl, totsz;
1.6 schwarze 807: char buf[2];
1.5 schwarze 808:
809: /*
1.48 schwarze 810: * Almost the same code as in tblcalc_number():
811: * First find the position of the decimal point.
1.5 schwarze 812: */
813:
1.7 schwarze 814: assert(dp->string);
1.48 schwarze 815: lastdigit = lastpoint = NULL;
816: for (cp = dp->string; cp[0] != '\0'; cp++) {
817: if (cp[0] == '\\' && cp[1] == '&') {
818: lastdigit = lastpoint = cp;
819: break;
820: } else if (cp[0] == opts->decimal &&
821: (isdigit((unsigned char)cp[1]) ||
822: (cp > dp->string && isdigit((unsigned char)cp[-1]))))
823: lastpoint = cp;
824: else if (isdigit((unsigned char)cp[0]))
825: lastdigit = cp;
826: }
827:
828: /* Then measure both widths. */
1.5 schwarze 829:
1.48 schwarze 830: padl = 0;
831: totsz = term_strlen(tp, dp->string);
832: if (lastdigit != NULL) {
833: if (lastpoint == NULL)
834: lastpoint = lastdigit + 1;
835: intsz = 0;
836: buf[1] = '\0';
837: for (cp = dp->string; cp < lastpoint; cp++) {
838: buf[0] = cp[0];
839: intsz += term_strlen(tp, buf);
840: }
841:
842: /*
843: * Pad left to match the decimal position,
844: * but avoid exceeding the total column width.
845: */
846:
847: if (col->decimal > intsz && col->width > totsz) {
848: padl = col->decimal - intsz;
849: if (padl + totsz > col->width)
850: padl = col->width - totsz;
851: }
1.5 schwarze 852:
1.48 schwarze 853: /* If it is not a number, simply center the string. */
1.5 schwarze 854:
1.48 schwarze 855: } else if (col->width > totsz)
856: padl = (col->width - totsz) / 2;
857:
1.50 schwarze 858: tbl_fill_char(tp, ASCII_NBRSP, padl);
1.17 schwarze 859: tbl_word(tp, dp);
1.48 schwarze 860:
861: /* Pad right to fill the column. */
862:
863: if (col->width > padl + totsz)
1.50 schwarze 864: tbl_fill_char(tp, ASCII_NBRSP, col->width - padl - totsz);
1.5 schwarze 865: }
1.1 schwarze 866:
1.17 schwarze 867: static void
868: tbl_word(struct termp *tp, const struct tbl_dat *dp)
869: {
1.26 schwarze 870: int prev_font;
1.17 schwarze 871:
1.26 schwarze 872: prev_font = tp->fonti;
1.17 schwarze 873: if (dp->layout->flags & TBL_CELL_BOLD)
874: term_fontpush(tp, TERMFONT_BOLD);
875: else if (dp->layout->flags & TBL_CELL_ITALIC)
876: term_fontpush(tp, TERMFONT_UNDER);
877:
878: term_word(tp, dp->string);
879:
880: term_fontpopq(tp, prev_font);
881: }