Annotation of src/usr.bin/mandoc/tbl_term.c, Revision 1.5
1.5 ! schwarze 1: /* $Id: tbl_term.c,v 1.1 2011/01/02 12:21:07 kristaps Exp $ */
1.1 schwarze 2: /*
1.5 ! schwarze 3: * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@kth.se>
1.1 schwarze 4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
8: *
9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16: */
17: #include <assert.h>
18: #include <stdio.h>
19: #include <stdlib.h>
20: #include <string.h>
21:
1.5 ! schwarze 22: #include "mandoc.h"
1.2 schwarze 23: #include "out.h"
24: #include "term.h"
1.1 schwarze 25:
26: /* FIXME: `n' modifier doesn't always do the right thing. */
27: /* FIXME: `n' modifier doesn't use the cell-spacing buffer. */
28:
1.5 ! schwarze 29: static inline void tbl_char(struct termp *, char, int);
! 30: static void tbl_hframe(struct termp *,
! 31: const struct tbl_span *);
! 32: static void tbl_data_number(struct termp *,
! 33: const struct tbl *,
! 34: const struct tbl_dat *,
! 35: const struct termp_tbl *);
! 36: static void tbl_data_literal(struct termp *,
! 37: const struct tbl_dat *,
! 38: const struct termp_tbl *);
! 39: static void tbl_data(struct termp *, const struct tbl *,
! 40: const struct tbl_dat *,
! 41: const struct termp_tbl *);
! 42: static void tbl_spanner(struct termp *,
! 43: const struct tbl_head *);
! 44: static void tbl_hrule(struct termp *,
! 45: const struct tbl_span *);
! 46: static void tbl_vframe(struct termp *,
! 47: const struct tbl *);
! 48: static void tbl_calc(struct termp *,
! 49: const struct tbl_span *);
! 50: static void tbl_calc_data(struct termp *,
! 51: const struct tbl *,
! 52: const struct tbl_dat *,
! 53: struct termp_tbl *);
! 54: static void tbl_calc_data_literal(struct termp *,
! 55: const struct tbl_dat *,
! 56: struct termp_tbl *);
! 57: static void tbl_calc_data_number(struct termp *,
! 58: const struct tbl *,
! 59: const struct tbl_dat *,
! 60: struct termp_tbl *);
! 61:
! 62: void
! 63: term_tbl(struct termp *tp, const struct tbl_span *sp)
! 64: {
! 65: const struct tbl_head *hp;
! 66: const struct tbl_dat *dp;
! 67:
! 68: /* Inhibit printing of spaces: we do padding ourselves. */
! 69:
! 70: tp->flags |= TERMP_NONOSPACE;
! 71: tp->flags |= TERMP_NOSPACE;
1.1 schwarze 72:
73: /*
1.5 ! schwarze 74: * The first time we're invoked for a given table block, create
! 75: * the termp_tbl structure. This contains the column
! 76: * configuration for the entire table, e.g., table-wide column
! 77: * width, decimal point, etc.
1.1 schwarze 78: */
79:
1.5 ! schwarze 80: if (TBL_SPAN_FIRST & sp->flags) {
! 81: assert(NULL == tp->tbl);
! 82: tp->tbl = calloc
! 83: (sp->tbl->cols, sizeof(struct termp_tbl));
! 84: if (NULL == tp->tbl) {
! 85: perror(NULL);
! 86: exit(EXIT_FAILURE);
! 87: }
! 88: tbl_calc(tp, sp);
! 89:
! 90: /* Flush out any preceding data. */
! 91: term_flushln(tp);
! 92: }
1.2 schwarze 93:
1.5 ! schwarze 94: /* Horizontal frame at the start of boxed tables. */
1.1 schwarze 95:
1.5 ! schwarze 96: if (TBL_SPAN_FIRST & sp->flags)
! 97: tbl_hframe(tp, sp);
! 98:
! 99: /* Vertical frame at the start of each row. */
! 100:
! 101: tbl_vframe(tp, sp->tbl);
1.1 schwarze 102:
103: /*
1.5 ! schwarze 104: * Now print the actual data itself depending on the span type.
! 105: * Spanner spans get a horizontal rule; data spanners have their
! 106: * data printed by matching data to header.
1.1 schwarze 107: */
108:
1.5 ! schwarze 109: switch (sp->pos) {
! 110: case (TBL_SPAN_HORIZ):
! 111: /* FALLTHROUGH */
! 112: case (TBL_SPAN_DHORIZ):
! 113: tbl_hrule(tp, sp);
! 114: break;
! 115: case (TBL_SPAN_DATA):
! 116: /* Iterate over template headers. */
! 117: dp = sp->first;
! 118: for (hp = sp->head; hp; hp = hp->next) {
! 119: switch (hp->pos) {
1.1 schwarze 120: case (TBL_HEAD_VERT):
121: /* FALLTHROUGH */
122: case (TBL_HEAD_DVERT):
1.5 ! schwarze 123: tbl_spanner(tp, hp);
! 124: continue;
1.1 schwarze 125: case (TBL_HEAD_DATA):
126: break;
127: }
1.5 ! schwarze 128: tbl_data(tp, sp->tbl, dp,
! 129: &tp->tbl[hp->ident]);
! 130:
! 131: /* Go to the next data cell. */
! 132: if (dp)
! 133: dp = dp->next;
1.1 schwarze 134: }
1.5 ! schwarze 135: break;
1.1 schwarze 136: }
137:
1.5 ! schwarze 138: tbl_vframe(tp, sp->tbl);
! 139: term_flushln(tp);
1.1 schwarze 140:
1.5 ! schwarze 141: /*
! 142: * If we're the last row, clean up after ourselves: clear the
! 143: * existing table configuration and set it to NULL.
! 144: */
1.1 schwarze 145:
1.5 ! schwarze 146: if (TBL_SPAN_LAST & sp->flags) {
! 147: tbl_hframe(tp, sp);
! 148: assert(tp->tbl);
! 149: free(tp->tbl);
! 150: tp->tbl = NULL;
1.1 schwarze 151: }
152:
1.5 ! schwarze 153: tp->flags &= ~TERMP_NONOSPACE;
1.1 schwarze 154:
155: }
156:
157: static void
1.5 ! schwarze 158: tbl_hrule(struct termp *tp, const struct tbl_span *sp)
1.1 schwarze 159: {
1.5 ! schwarze 160: const struct tbl_head *hp;
! 161: char c;
! 162: int width;
1.1 schwarze 163:
164: /*
165: * An hrule extends across the entire table and is demarked by a
166: * standalone `_' or whatnot in lieu of a table row. Spanning
167: * headers are marked by a `+', as are table boundaries.
168: */
169:
170: c = '-';
1.5 ! schwarze 171: if (TBL_SPAN_DHORIZ == sp->pos)
1.1 schwarze 172: c = '=';
173:
174: /* FIXME: don't use `+' between data and a spanner! */
175:
1.5 ! schwarze 176: for (hp = sp->head; hp; hp = hp->next) {
! 177: width = tp->tbl[hp->ident].width;
! 178: switch (hp->pos) {
1.1 schwarze 179: case (TBL_HEAD_DATA):
1.5 ! schwarze 180: tbl_char(tp, c, width);
1.1 schwarze 181: break;
182: case (TBL_HEAD_DVERT):
1.5 ! schwarze 183: tbl_char(tp, '+', width);
1.1 schwarze 184: /* FALLTHROUGH */
185: case (TBL_HEAD_VERT):
1.5 ! schwarze 186: tbl_char(tp, '+', width);
1.1 schwarze 187: break;
188: default:
189: abort();
190: /* NOTREACHED */
191: }
192: }
193: }
194:
195: static void
1.5 ! schwarze 196: tbl_hframe(struct termp *tp, const struct tbl_span *sp)
1.1 schwarze 197: {
1.5 ! schwarze 198: const struct tbl_head *hp;
! 199: int width;
1.1 schwarze 200:
1.5 ! schwarze 201: if ( ! (TBL_OPT_BOX & sp->tbl->opts ||
! 202: TBL_OPT_DBOX & sp->tbl->opts))
1.1 schwarze 203: return;
204:
205: /*
206: * Print out the horizontal part of a frame or double frame. A
207: * double frame has an unbroken `-' outer line the width of the
208: * table, bordered by `+'. The frame (or inner frame, in the
209: * case of the double frame) is a `-' bordered by `+' and broken
210: * by `+' whenever a span is encountered.
211: */
212:
1.5 ! schwarze 213: if (TBL_OPT_DBOX & sp->tbl->opts) {
! 214: term_word(tp, "+");
! 215: for (hp = sp->head; hp; hp = hp->next) {
! 216: width = tp->tbl[hp->ident].width;
! 217: tbl_char(tp, '-', width);
! 218: }
! 219: term_word(tp, "+");
! 220: term_flushln(tp);
1.1 schwarze 221: }
222:
1.5 ! schwarze 223: term_word(tp, "+");
! 224: for (hp = sp->head; hp; hp = hp->next) {
! 225: width = tp->tbl[hp->ident].width;
! 226: switch (hp->pos) {
1.1 schwarze 227: case (TBL_HEAD_DATA):
1.5 ! schwarze 228: tbl_char(tp, '-', width);
1.1 schwarze 229: break;
230: default:
1.5 ! schwarze 231: tbl_char(tp, '+', width);
1.1 schwarze 232: break;
233: }
234: }
1.5 ! schwarze 235: term_word(tp, "+");
! 236: term_flushln(tp);
1.1 schwarze 237: }
238:
239: static void
1.5 ! schwarze 240: tbl_data(struct termp *tp, const struct tbl *tbl,
! 241: const struct tbl_dat *dp,
! 242: const struct termp_tbl *tbp)
1.1 schwarze 243: {
1.5 ! schwarze 244: enum tbl_cellt pos;
1.1 schwarze 245:
1.5 ! schwarze 246: if (NULL == dp) {
! 247: tbl_char(tp, ASCII_NBRSP, tbp->width);
1.1 schwarze 248: return;
1.5 ! schwarze 249: }
1.1 schwarze 250:
1.5 ! schwarze 251: switch (dp->pos) {
! 252: case (TBL_DATA_NONE):
! 253: tbl_char(tp, ASCII_NBRSP, tbp->width);
! 254: return;
! 255: case (TBL_DATA_HORIZ):
! 256: /* FALLTHROUGH */
! 257: case (TBL_DATA_NHORIZ):
! 258: tbl_char(tp, '-', tbp->width);
! 259: return;
! 260: case (TBL_DATA_NDHORIZ):
1.1 schwarze 261: /* FALLTHROUGH */
1.5 ! schwarze 262: case (TBL_DATA_DHORIZ):
! 263: tbl_char(tp, '=', tbp->width);
! 264: return;
1.1 schwarze 265: default:
266: break;
267: }
1.5 ! schwarze 268:
! 269: pos = dp->layout ? dp->layout->pos : TBL_CELL_LEFT;
1.1 schwarze 270:
1.5 ! schwarze 271: switch (pos) {
1.1 schwarze 272: case (TBL_CELL_HORIZ):
1.5 ! schwarze 273: tbl_char(tp, '-', tbp->width);
! 274: break;
1.1 schwarze 275: case (TBL_CELL_DHORIZ):
1.5 ! schwarze 276: tbl_char(tp, '=', tbp->width);
1.1 schwarze 277: break;
278: case (TBL_CELL_LONG):
279: /* FALLTHROUGH */
280: case (TBL_CELL_CENTRE):
281: /* FALLTHROUGH */
282: case (TBL_CELL_LEFT):
283: /* FALLTHROUGH */
284: case (TBL_CELL_RIGHT):
1.5 ! schwarze 285: tbl_data_literal(tp, dp, tbp);
1.1 schwarze 286: break;
287: case (TBL_CELL_NUMBER):
1.5 ! schwarze 288: tbl_data_number(tp, tbl, dp, tbp);
1.4 schwarze 289: break;
1.1 schwarze 290: default:
291: abort();
292: /* NOTREACHED */
293: }
294: }
295: static void
1.5 ! schwarze 296: tbl_spanner(struct termp *tp, const struct tbl_head *hp)
1.1 schwarze 297: {
298:
1.5 ! schwarze 299: switch (hp->pos) {
! 300: case (TBL_HEAD_VERT):
! 301: term_word(tp, "|");
! 302: break;
! 303: case (TBL_HEAD_DVERT):
! 304: term_word(tp, "||");
! 305: break;
! 306: default:
! 307: break;
! 308: }
1.1 schwarze 309: }
310:
311: static void
1.5 ! schwarze 312: tbl_vframe(struct termp *tp, const struct tbl *tbl)
1.1 schwarze 313: {
1.5 ! schwarze 314: /* Always just a single vertical line. */
1.1 schwarze 315:
1.5 ! schwarze 316: if (TBL_OPT_BOX & tbl->opts || TBL_OPT_DBOX & tbl->opts)
! 317: term_word(tp, "|");
! 318: }
1.1 schwarze 319:
1.5 ! schwarze 320: static inline void
! 321: tbl_char(struct termp *tp, char c, int len)
! 322: {
! 323: int i, sz;
! 324: char cp[2];
1.1 schwarze 325:
1.5 ! schwarze 326: cp[0] = c;
! 327: cp[1] = '\0';
1.1 schwarze 328:
1.5 ! schwarze 329: sz = term_strlen(tp, cp);
1.1 schwarze 330:
1.5 ! schwarze 331: for (i = 0; i < len; i += sz)
! 332: term_word(tp, cp);
1.1 schwarze 333: }
334:
335: static void
1.5 ! schwarze 336: tbl_data_literal(struct termp *tp,
! 337: const struct tbl_dat *dp,
! 338: const struct termp_tbl *tblp)
1.1 schwarze 339: {
1.5 ! schwarze 340: int padl, padr, ssz;
! 341: enum tbl_cellt pos;
1.1 schwarze 342:
343: padl = padr = 0;
344:
1.5 ! schwarze 345: pos = dp->layout ? dp->layout->pos : TBL_CELL_LEFT;
! 346: ssz = term_len(tp, 1);
! 347:
! 348: switch (pos) {
1.1 schwarze 349: case (TBL_CELL_LONG):
1.5 ! schwarze 350: padl = ssz;
! 351: padr = tblp->width - term_strlen(tp, dp->string) - ssz;
1.1 schwarze 352: break;
353: case (TBL_CELL_CENTRE):
1.5 ! schwarze 354: padl = tblp->width - term_strlen(tp, dp->string);
1.1 schwarze 355: if (padl % 2)
356: padr++;
357: padl /= 2;
358: padr += padl;
359: break;
360: case (TBL_CELL_RIGHT):
1.5 ! schwarze 361: padl = tblp->width - term_strlen(tp, dp->string);
1.1 schwarze 362: break;
363: default:
1.5 ! schwarze 364: padr = tblp->width - term_strlen(tp, dp->string);
1.1 schwarze 365: break;
366: }
367:
1.5 ! schwarze 368: tbl_char(tp, ASCII_NBRSP, padl);
! 369: term_word(tp, dp->string);
! 370: tbl_char(tp, ASCII_NBRSP, padr);
1.1 schwarze 371: }
372:
1.5 ! schwarze 373: static void
! 374: tbl_data_number(struct termp *tp, const struct tbl *tbl,
! 375: const struct tbl_dat *dp,
! 376: const struct termp_tbl *tblp)
! 377: {
! 378: char *decp, buf[2];
! 379: int d, padl, sz, psz, ssz, i;
! 380:
! 381: /*
! 382: * See calc_data_number(). Left-pad by taking the offset of our
! 383: * and the maximum decimal; right-pad by the remaining amount.
! 384: */
! 385:
! 386: sz = term_strlen(tp, dp->string);
! 387: psz = term_strlen(tp, ".");
! 388:
! 389: if (NULL != (decp = strchr(dp->string, tbl->decimal))) {
! 390: buf[1] = '\0';
! 391: for (ssz = i = 0; decp != &dp->string[i]; i++) {
! 392: buf[0] = dp->string[i];
! 393: ssz += term_strlen(tp, buf);
! 394: }
! 395: d = ssz + psz;
! 396: } else
! 397: d = sz + psz;
! 398:
! 399: assert(d <= tblp->decimal);
! 400: assert(sz - d <= tblp->width - tblp->decimal);
! 401:
! 402: padl = tblp->decimal - d + term_len(tp, 1);
! 403: assert(tblp->width - sz - padl);
! 404:
! 405: tbl_char(tp, ASCII_NBRSP, padl);
! 406: term_word(tp, dp->string);
! 407: tbl_char(tp, ASCII_NBRSP, tblp->width - sz - padl);
! 408: }
1.1 schwarze 409:
410: static void
1.5 ! schwarze 411: tbl_calc(struct termp *tp, const struct tbl_span *sp)
1.1 schwarze 412: {
1.5 ! schwarze 413: const struct tbl_dat *dp;
! 414: const struct tbl_head *hp;
! 415: struct termp_tbl *p;
1.1 schwarze 416:
1.5 ! schwarze 417: /* Calculate width as the max of column cells' widths. */
! 418:
! 419: hp = sp->head;
! 420:
! 421: for ( ; sp; sp = sp->next) {
! 422: if (TBL_SPAN_DATA != sp->pos)
! 423: continue;
! 424:
! 425: for (dp = sp->first; dp; dp = dp->next) {
! 426: if (NULL == dp->layout)
! 427: continue;
! 428: p = &tp->tbl[dp->layout->head->ident];
! 429: tbl_calc_data(tp, sp->tbl, dp, p);
! 430: }
1.1 schwarze 431: }
432:
1.5 ! schwarze 433: /* Calculate width as the simple spanner value. */
! 434:
! 435: for ( ; hp; hp = hp->next)
! 436: switch (hp->pos) {
! 437: case (TBL_HEAD_VERT):
! 438: tp->tbl[hp->ident].width = term_len(tp, 1);
! 439: break;
! 440: case (TBL_HEAD_DVERT):
! 441: tp->tbl[hp->ident].width = term_len(tp, 2);
! 442: break;
! 443: default:
! 444: break;
! 445: }
! 446: }
! 447:
! 448: static void
! 449: tbl_calc_data(struct termp *tp, const struct tbl *tbl,
! 450: const struct tbl_dat *dp, struct termp_tbl *tblp)
! 451: {
! 452: int sz;
! 453:
! 454: /* Branch down into data sub-types. */
1.1 schwarze 455:
1.5 ! schwarze 456: switch (dp->layout->pos) {
1.1 schwarze 457: case (TBL_CELL_HORIZ):
458: /* FALLTHROUGH */
459: case (TBL_CELL_DHORIZ):
1.5 ! schwarze 460: sz = term_len(tp, 1);
! 461: if (tblp->width < sz)
! 462: tblp->width = sz;
1.1 schwarze 463: break;
464: case (TBL_CELL_LONG):
465: /* FALLTHROUGH */
466: case (TBL_CELL_CENTRE):
467: /* FALLTHROUGH */
468: case (TBL_CELL_LEFT):
469: /* FALLTHROUGH */
470: case (TBL_CELL_RIGHT):
1.5 ! schwarze 471: tbl_calc_data_literal(tp, dp, tblp);
1.1 schwarze 472: break;
473: case (TBL_CELL_NUMBER):
1.5 ! schwarze 474: tbl_calc_data_number(tp, tbl, dp, tblp);
1.1 schwarze 475: break;
476: default:
477: abort();
478: /* NOTREACHED */
479: }
480: }
481:
1.5 ! schwarze 482: static void
! 483: tbl_calc_data_number(struct termp *tp, const struct tbl *tbl,
! 484: const struct tbl_dat *dp, struct termp_tbl *tblp)
! 485: {
! 486: int sz, d, psz, i, ssz;
! 487: char *cp, buf[2];
! 488:
! 489: /*
! 490: * First calculate number width and decimal place (last + 1 for
! 491: * no-decimal numbers). If the stored decimal is subsequent
! 492: * ours, make our size longer by that difference
! 493: * (right-"shifting"); similarly, if ours is subsequent the
! 494: * stored, then extend the stored size by the difference.
! 495: * Finally, re-assign the stored values.
! 496: */
! 497:
! 498: /* TODO: use spacing modifier. */
! 499:
! 500: assert(dp->string);
! 501: sz = term_strlen(tp, dp->string);
! 502: psz = term_strlen(tp, ".");
! 503:
! 504: if (NULL != (cp = strchr(dp->string, tbl->decimal))) {
! 505: buf[1] = '\0';
! 506: for (ssz = i = 0; cp != &dp->string[i]; i++) {
! 507: buf[0] = dp->string[i];
! 508: ssz += term_strlen(tp, buf);
! 509: }
! 510: d = ssz + psz;
! 511: } else
! 512: d = sz + psz;
! 513:
! 514: sz += term_len(tp, 2);
! 515:
! 516: if (tblp->decimal > d) {
! 517: sz += tblp->decimal - d;
! 518: d = tblp->decimal;
! 519: } else
! 520: tblp->width += d - tblp->decimal;
! 521:
! 522: if (sz > tblp->width)
! 523: tblp->width = sz;
! 524: if (d > tblp->decimal)
! 525: tblp->decimal = d;
! 526: }
1.1 schwarze 527:
528: static void
1.5 ! schwarze 529: tbl_calc_data_literal(struct termp *tp,
! 530: const struct tbl_dat *dp,
! 531: struct termp_tbl *tblp)
1.1 schwarze 532: {
1.5 ! schwarze 533: int sz, bufsz, spsz;
1.1 schwarze 534:
1.5 ! schwarze 535: /*
! 536: * Calculate our width and use the spacing, with a minimum
! 537: * spacing dictated by position (centre, e.g,. gets a space on
! 538: * either side, while right/left get a single adjacent space).
! 539: */
! 540:
! 541: assert(dp->string);
! 542: sz = term_strlen(tp, dp->string);
! 543:
! 544: switch (dp->layout->pos) {
! 545: case (TBL_CELL_LONG):
! 546: /* FALLTHROUGH */
! 547: case (TBL_CELL_CENTRE):
! 548: bufsz = term_len(tp, 2);
1.1 schwarze 549: break;
550: default:
1.5 ! schwarze 551: bufsz = term_len(tp, 1);
1.1 schwarze 552: break;
553: }
554:
1.5 ! schwarze 555: spsz = 0;
! 556: if (dp->layout->spacing)
! 557: spsz = term_len(tp, dp->layout->spacing);
1.1 schwarze 558:
1.5 ! schwarze 559: if (spsz)
! 560: bufsz = bufsz > spsz ? bufsz : spsz;
1.1 schwarze 561:
1.5 ! schwarze 562: sz += bufsz;
! 563: if (tblp->width < sz)
! 564: tblp->width = sz;
1.1 schwarze 565: }