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