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