Annotation of src/usr.bin/mandoc/tbl_term.c, Revision 1.20
1.20 ! schwarze 1: /* $OpenBSD: tbl_term.c,v 1.19 2014/10/14 18:16:57 schwarze Exp $ */
1.1 schwarze 2: /*
1.9 schwarze 3: * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
1.15 schwarze 4: * Copyright (c) 2011, 2012, 2014 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>
21: #include <stdio.h>
22: #include <stdlib.h>
23: #include <string.h>
24:
1.5 schwarze 25: #include "mandoc.h"
1.2 schwarze 26: #include "out.h"
27: #include "term.h"
1.1 schwarze 28:
1.6 schwarze 29: static size_t term_tbl_len(size_t, void *);
30: static size_t term_tbl_strlen(const char *, void *);
31: static void tbl_char(struct termp *, char, size_t);
1.14 schwarze 32: static void tbl_data(struct termp *, const struct tbl_opts *,
1.16 schwarze 33: const struct tbl_dat *,
1.6 schwarze 34: const struct roffcol *);
1.10 schwarze 35: static size_t tbl_rulewidth(struct termp *, const struct tbl_head *);
36: static void tbl_hframe(struct termp *, const struct tbl_span *, int);
1.16 schwarze 37: static void tbl_literal(struct termp *, const struct tbl_dat *,
1.6 schwarze 38: const struct roffcol *);
1.16 schwarze 39: static void tbl_number(struct termp *, const struct tbl_opts *,
40: const struct tbl_dat *,
1.6 schwarze 41: const struct roffcol *);
42: static void tbl_hrule(struct termp *, const struct tbl_span *);
43: static void tbl_vrule(struct termp *, const struct tbl_head *);
1.17 schwarze 44: static void tbl_word(struct termp *, const struct tbl_dat *);
1.1 schwarze 45:
1.6 schwarze 46:
47: static size_t
48: term_tbl_strlen(const char *p, void *arg)
49: {
50:
51: return(term_strlen((const struct termp *)arg, p));
52: }
53:
54: static size_t
55: term_tbl_len(size_t sz, void *arg)
56: {
57:
58: return(term_len((const struct termp *)arg, sz));
59: }
1.5 schwarze 60:
61: void
62: term_tbl(struct termp *tp, const struct tbl_span *sp)
63: {
1.6 schwarze 64: const struct tbl_head *hp;
65: const struct tbl_dat *dp;
66: struct roffcol *col;
1.7 schwarze 67: int spans;
1.16 schwarze 68: size_t rmargin, maxrmargin;
1.6 schwarze 69:
70: rmargin = tp->rmargin;
71: maxrmargin = tp->maxrmargin;
72:
73: tp->rmargin = tp->maxrmargin = TERM_MAXMARGIN;
1.5 schwarze 74:
75: /* Inhibit printing of spaces: we do padding ourselves. */
76:
77: tp->flags |= TERMP_NONOSPACE;
78: tp->flags |= TERMP_NOSPACE;
1.1 schwarze 79:
80: /*
1.6 schwarze 81: * The first time we're invoked for a given table block,
82: * calculate the table widths and decimal positions.
1.1 schwarze 83: */
84:
1.5 schwarze 85: if (TBL_SPAN_FIRST & sp->flags) {
1.6 schwarze 86: term_flushln(tp);
87:
88: tp->tbl.len = term_tbl_len;
89: tp->tbl.slen = term_tbl_strlen;
90: tp->tbl.arg = tp;
1.5 schwarze 91:
1.18 schwarze 92: tblcalc(&tp->tbl, sp, rmargin - tp->offset);
1.5 schwarze 93: }
1.2 schwarze 94:
1.5 schwarze 95: /* Horizontal frame at the start of boxed tables. */
1.1 schwarze 96:
1.10 schwarze 97: if (TBL_SPAN_FIRST & sp->flags) {
1.14 schwarze 98: if (TBL_OPT_DBOX & sp->opts->opts)
1.10 schwarze 99: tbl_hframe(tp, sp, 1);
1.14 schwarze 100: if (TBL_OPT_DBOX & sp->opts->opts ||
101: TBL_OPT_BOX & sp->opts->opts)
1.10 schwarze 102: tbl_hframe(tp, sp, 0);
103: }
1.5 schwarze 104:
105: /* Vertical frame at the start of each row. */
106:
1.15 schwarze 107: if ((TBL_OPT_BOX | TBL_OPT_DBOX) & sp->opts->opts ||
1.19 schwarze 108: (sp->head != NULL && sp->head->vert))
1.10 schwarze 109: term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
1.16 schwarze 110: TBL_SPAN_DHORIZ == sp->pos ? "+" : "|");
1.1 schwarze 111:
112: /*
1.5 schwarze 113: * Now print the actual data itself depending on the span type.
114: * Spanner spans get a horizontal rule; data spanners have their
115: * data printed by matching data to header.
1.1 schwarze 116: */
117:
1.5 schwarze 118: switch (sp->pos) {
1.16 schwarze 119: case TBL_SPAN_HORIZ:
1.5 schwarze 120: /* FALLTHROUGH */
1.16 schwarze 121: case TBL_SPAN_DHORIZ:
1.5 schwarze 122: tbl_hrule(tp, sp);
123: break;
1.16 schwarze 124: case TBL_SPAN_DATA:
1.5 schwarze 125: /* Iterate over template headers. */
126: dp = sp->first;
1.7 schwarze 127: spans = 0;
1.5 schwarze 128: for (hp = sp->head; hp; hp = hp->next) {
1.11 schwarze 129:
1.16 schwarze 130: /*
1.7 schwarze 131: * If the current data header is invoked during
132: * a spanner ("spans" > 0), don't emit anything
133: * at all.
134: */
1.6 schwarze 135:
1.7 schwarze 136: if (--spans >= 0)
137: continue;
138:
1.11 schwarze 139: /* Separate columns. */
1.10 schwarze 140:
1.11 schwarze 141: if (NULL != hp->prev)
142: tbl_vrule(tp, hp);
1.10 schwarze 143:
1.6 schwarze 144: col = &tp->tbl.cols[hp->ident];
1.14 schwarze 145: tbl_data(tp, sp->opts, dp, col);
1.5 schwarze 146:
1.16 schwarze 147: /*
1.7 schwarze 148: * Go to the next data cell and assign the
149: * number of subsequent spans, if applicable.
150: */
151:
152: if (dp) {
153: spans = dp->spans;
1.5 schwarze 154: dp = dp->next;
1.7 schwarze 155: }
1.1 schwarze 156: }
1.5 schwarze 157: break;
1.1 schwarze 158: }
159:
1.10 schwarze 160: /* Vertical frame at the end of each row. */
161:
1.15 schwarze 162: if ((TBL_OPT_BOX | TBL_OPT_DBOX) & sp->opts->opts ||
163: sp->layout->vert)
1.10 schwarze 164: term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
1.16 schwarze 165: TBL_SPAN_DHORIZ == sp->pos ? "+" : " |");
1.5 schwarze 166: term_flushln(tp);
1.1 schwarze 167:
1.5 schwarze 168: /*
169: * If we're the last row, clean up after ourselves: clear the
170: * existing table configuration and set it to NULL.
171: */
1.1 schwarze 172:
1.5 schwarze 173: if (TBL_SPAN_LAST & sp->flags) {
1.14 schwarze 174: if (TBL_OPT_DBOX & sp->opts->opts ||
175: TBL_OPT_BOX & sp->opts->opts) {
1.10 schwarze 176: tbl_hframe(tp, sp, 0);
1.13 schwarze 177: tp->skipvsp = 1;
178: }
1.14 schwarze 179: if (TBL_OPT_DBOX & sp->opts->opts) {
1.10 schwarze 180: tbl_hframe(tp, sp, 1);
1.13 schwarze 181: tp->skipvsp = 2;
182: }
1.6 schwarze 183: assert(tp->tbl.cols);
184: free(tp->tbl.cols);
185: tp->tbl.cols = NULL;
1.1 schwarze 186: }
187:
1.5 schwarze 188: tp->flags &= ~TERMP_NONOSPACE;
1.6 schwarze 189: tp->rmargin = rmargin;
190: tp->maxrmargin = maxrmargin;
1.1 schwarze 191:
192: }
193:
1.10 schwarze 194: /*
195: * Horizontal rules extend across the entire table.
196: * Calculate the width by iterating over columns.
197: */
198: static size_t
199: tbl_rulewidth(struct termp *tp, const struct tbl_head *hp)
200: {
201: size_t width;
202:
203: width = tp->tbl.cols[hp->ident].width;
1.11 schwarze 204:
205: /* Account for leading blanks. */
206: if (hp->prev)
207: width += 2 - hp->vert;
208:
209: /* Account for trailing blank. */
210: width++;
211:
1.10 schwarze 212: return(width);
213: }
214:
215: /*
216: * Rules inside the table can be single or double
217: * and have crossings with vertical rules marked with pluses.
218: */
1.1 schwarze 219: static void
1.5 schwarze 220: tbl_hrule(struct termp *tp, const struct tbl_span *sp)
1.1 schwarze 221: {
1.5 schwarze 222: const struct tbl_head *hp;
223: char c;
1.1 schwarze 224:
225: c = '-';
1.5 schwarze 226: if (TBL_SPAN_DHORIZ == sp->pos)
1.1 schwarze 227: c = '=';
228:
1.11 schwarze 229: for (hp = sp->head; hp; hp = hp->next) {
230: if (hp->prev && hp->vert)
231: tbl_char(tp, '+', hp->vert);
232: tbl_char(tp, c, tbl_rulewidth(tp, hp));
233: }
1.1 schwarze 234: }
235:
1.10 schwarze 236: /*
237: * Rules above and below the table are always single
238: * and have an additional plus at the beginning and end.
239: * For double frames, this function is called twice,
240: * and the outer one does not have crossings.
241: */
1.1 schwarze 242: static void
1.10 schwarze 243: tbl_hframe(struct termp *tp, const struct tbl_span *sp, int outer)
1.1 schwarze 244: {
1.5 schwarze 245: const struct tbl_head *hp;
1.1 schwarze 246:
1.5 schwarze 247: term_word(tp, "+");
1.11 schwarze 248: for (hp = sp->head; hp; hp = hp->next) {
249: if (hp->prev && hp->vert)
250: tbl_char(tp, (outer ? '-' : '+'), hp->vert);
251: tbl_char(tp, '-', tbl_rulewidth(tp, hp));
252: }
1.5 schwarze 253: term_word(tp, "+");
254: term_flushln(tp);
1.1 schwarze 255: }
256:
257: static void
1.14 schwarze 258: tbl_data(struct termp *tp, const struct tbl_opts *opts,
1.16 schwarze 259: const struct tbl_dat *dp,
260: const struct roffcol *col)
1.1 schwarze 261: {
262:
1.5 schwarze 263: if (NULL == dp) {
1.6 schwarze 264: tbl_char(tp, ASCII_NBRSP, col->width);
1.1 schwarze 265: return;
1.5 schwarze 266: }
1.7 schwarze 267: assert(dp->layout);
1.1 schwarze 268:
1.5 schwarze 269: switch (dp->pos) {
1.16 schwarze 270: case TBL_DATA_NONE:
1.6 schwarze 271: tbl_char(tp, ASCII_NBRSP, col->width);
1.5 schwarze 272: return;
1.16 schwarze 273: case TBL_DATA_HORIZ:
1.5 schwarze 274: /* FALLTHROUGH */
1.16 schwarze 275: case TBL_DATA_NHORIZ:
1.6 schwarze 276: tbl_char(tp, '-', col->width);
1.5 schwarze 277: return;
1.16 schwarze 278: case TBL_DATA_NDHORIZ:
1.1 schwarze 279: /* FALLTHROUGH */
1.16 schwarze 280: case TBL_DATA_DHORIZ:
1.6 schwarze 281: tbl_char(tp, '=', col->width);
1.5 schwarze 282: return;
1.1 schwarze 283: default:
284: break;
285: }
1.16 schwarze 286:
1.7 schwarze 287: switch (dp->layout->pos) {
1.16 schwarze 288: case TBL_CELL_HORIZ:
1.6 schwarze 289: tbl_char(tp, '-', col->width);
1.5 schwarze 290: break;
1.16 schwarze 291: case TBL_CELL_DHORIZ:
1.6 schwarze 292: tbl_char(tp, '=', col->width);
1.1 schwarze 293: break;
1.16 schwarze 294: case TBL_CELL_LONG:
1.1 schwarze 295: /* FALLTHROUGH */
1.16 schwarze 296: case TBL_CELL_CENTRE:
1.1 schwarze 297: /* FALLTHROUGH */
1.16 schwarze 298: case TBL_CELL_LEFT:
1.1 schwarze 299: /* FALLTHROUGH */
1.16 schwarze 300: case TBL_CELL_RIGHT:
1.6 schwarze 301: tbl_literal(tp, dp, col);
1.1 schwarze 302: break;
1.16 schwarze 303: case TBL_CELL_NUMBER:
1.14 schwarze 304: tbl_number(tp, opts, dp, col);
1.4 schwarze 305: break;
1.16 schwarze 306: case TBL_CELL_DOWN:
1.7 schwarze 307: tbl_char(tp, ASCII_NBRSP, col->width);
308: break;
1.1 schwarze 309: default:
310: abort();
311: /* NOTREACHED */
312: }
313: }
1.6 schwarze 314:
1.1 schwarze 315: static void
1.6 schwarze 316: tbl_vrule(struct termp *tp, const struct tbl_head *hp)
1.1 schwarze 317: {
318:
1.11 schwarze 319: tbl_char(tp, ASCII_NBRSP, 1);
320: if (0 < hp->vert)
321: tbl_char(tp, '|', hp->vert);
322: if (2 > hp->vert)
323: tbl_char(tp, ASCII_NBRSP, 2 - hp->vert);
1.1 schwarze 324: }
325:
326: static void
1.6 schwarze 327: tbl_char(struct termp *tp, char c, size_t len)
1.5 schwarze 328: {
1.6 schwarze 329: size_t i, sz;
1.5 schwarze 330: char cp[2];
1.1 schwarze 331:
1.5 schwarze 332: cp[0] = c;
333: cp[1] = '\0';
1.1 schwarze 334:
1.5 schwarze 335: sz = term_strlen(tp, cp);
1.1 schwarze 336:
1.5 schwarze 337: for (i = 0; i < len; i += sz)
338: term_word(tp, cp);
1.1 schwarze 339: }
340:
341: static void
1.16 schwarze 342: tbl_literal(struct termp *tp, const struct tbl_dat *dp,
1.6 schwarze 343: const struct roffcol *col)
1.1 schwarze 344: {
1.12 schwarze 345: struct tbl_head *hp;
346: size_t width, len, padl, padr;
347: int spans;
1.1 schwarze 348:
1.7 schwarze 349: assert(dp->string);
1.10 schwarze 350: len = term_strlen(tp, dp->string);
1.12 schwarze 351:
352: hp = dp->layout->head->next;
353: width = col->width;
354: for (spans = dp->spans; spans--; hp = hp->next)
355: width += tp->tbl.cols[hp->ident].width + 3;
356:
357: padr = width > len ? width - len : 0;
1.10 schwarze 358: padl = 0;
1.5 schwarze 359:
1.7 schwarze 360: switch (dp->layout->pos) {
1.16 schwarze 361: case TBL_CELL_LONG:
1.10 schwarze 362: padl = term_len(tp, 1);
363: padr = padr > padl ? padr - padl : 0;
1.1 schwarze 364: break;
1.16 schwarze 365: case TBL_CELL_CENTRE:
1.10 schwarze 366: if (2 > padr)
1.8 schwarze 367: break;
1.10 schwarze 368: padl = padr / 2;
1.8 schwarze 369: padr -= padl;
1.1 schwarze 370: break;
1.16 schwarze 371: case TBL_CELL_RIGHT:
1.10 schwarze 372: padl = padr;
373: padr = 0;
1.1 schwarze 374: break;
375: default:
376: break;
377: }
378:
1.5 schwarze 379: tbl_char(tp, ASCII_NBRSP, padl);
1.17 schwarze 380: tbl_word(tp, dp);
1.10 schwarze 381: tbl_char(tp, ASCII_NBRSP, padr);
1.1 schwarze 382: }
383:
1.5 schwarze 384: static void
1.14 schwarze 385: tbl_number(struct termp *tp, const struct tbl_opts *opts,
1.5 schwarze 386: const struct tbl_dat *dp,
1.6 schwarze 387: const struct roffcol *col)
1.5 schwarze 388: {
1.6 schwarze 389: char *cp;
390: char buf[2];
391: size_t sz, psz, ssz, d, padl;
392: int i;
1.5 schwarze 393:
394: /*
395: * See calc_data_number(). Left-pad by taking the offset of our
396: * and the maximum decimal; right-pad by the remaining amount.
397: */
398:
1.7 schwarze 399: assert(dp->string);
1.5 schwarze 400:
1.7 schwarze 401: sz = term_strlen(tp, dp->string);
1.5 schwarze 402:
1.14 schwarze 403: buf[0] = opts->decimal;
1.6 schwarze 404: buf[1] = '\0';
1.5 schwarze 405:
1.6 schwarze 406: psz = term_strlen(tp, buf);
1.5 schwarze 407:
1.14 schwarze 408: if (NULL != (cp = strrchr(dp->string, opts->decimal))) {
1.5 schwarze 409: buf[1] = '\0';
1.7 schwarze 410: for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
411: buf[0] = dp->string[i];
1.5 schwarze 412: ssz += term_strlen(tp, buf);
413: }
414: d = ssz + psz;
415: } else
416: d = sz + psz;
417:
1.20 ! schwarze 418: if (col->decimal > d && col->width > sz) {
! 419: padl = col->decimal - d;
! 420: if (padl + sz > col->width)
! 421: padl = col->width - sz;
! 422: tbl_char(tp, ASCII_NBRSP, padl);
! 423: } else
! 424: padl = 0;
1.17 schwarze 425: tbl_word(tp, dp);
1.10 schwarze 426: if (col->width > sz + padl)
427: tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
1.5 schwarze 428: }
1.1 schwarze 429:
1.17 schwarze 430: static void
431: tbl_word(struct termp *tp, const struct tbl_dat *dp)
432: {
433: const void *prev_font;
434:
435: prev_font = term_fontq(tp);
436: if (dp->layout->flags & TBL_CELL_BOLD)
437: term_fontpush(tp, TERMFONT_BOLD);
438: else if (dp->layout->flags & TBL_CELL_ITALIC)
439: term_fontpush(tp, TERMFONT_UNDER);
440:
441: term_word(tp, dp->string);
442:
443: term_fontpopq(tp, prev_font);
444: }