1241675Suqs/* $Id: tbl_term.c,v 1.21 2011/09/20 23:05:49 schwarze Exp $ */ 2241675Suqs/* 3241675Suqs * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4241675Suqs * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> 5241675Suqs * 6241675Suqs * Permission to use, copy, modify, and distribute this software for any 7241675Suqs * purpose with or without fee is hereby granted, provided that the above 8241675Suqs * copyright notice and this permission notice appear in all copies. 9241675Suqs * 10241675Suqs * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11241675Suqs * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12241675Suqs * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13241675Suqs * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14241675Suqs * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15241675Suqs * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16241675Suqs * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17241675Suqs */ 18241675Suqs#ifdef HAVE_CONFIG_H 19241675Suqs#include "config.h" 20241675Suqs#endif 21241675Suqs 22241675Suqs#include <assert.h> 23241675Suqs#include <stdio.h> 24241675Suqs#include <stdlib.h> 25241675Suqs#include <string.h> 26241675Suqs 27241675Suqs#include "mandoc.h" 28241675Suqs#include "out.h" 29241675Suqs#include "term.h" 30241675Suqs 31241675Suqsstatic size_t term_tbl_len(size_t, void *); 32241675Suqsstatic size_t term_tbl_strlen(const char *, void *); 33241675Suqsstatic void tbl_char(struct termp *, char, size_t); 34241675Suqsstatic void tbl_data(struct termp *, const struct tbl *, 35241675Suqs const struct tbl_dat *, 36241675Suqs const struct roffcol *); 37241675Suqsstatic size_t tbl_rulewidth(struct termp *, const struct tbl_head *); 38241675Suqsstatic void tbl_hframe(struct termp *, const struct tbl_span *, int); 39241675Suqsstatic void tbl_literal(struct termp *, const struct tbl_dat *, 40241675Suqs const struct roffcol *); 41241675Suqsstatic void tbl_number(struct termp *, const struct tbl *, 42241675Suqs const struct tbl_dat *, 43241675Suqs const struct roffcol *); 44241675Suqsstatic void tbl_hrule(struct termp *, const struct tbl_span *); 45241675Suqsstatic void tbl_vrule(struct termp *, const struct tbl_head *); 46241675Suqs 47241675Suqs 48241675Suqsstatic size_t 49241675Suqsterm_tbl_strlen(const char *p, void *arg) 50241675Suqs{ 51241675Suqs 52241675Suqs return(term_strlen((const struct termp *)arg, p)); 53241675Suqs} 54241675Suqs 55241675Suqsstatic size_t 56241675Suqsterm_tbl_len(size_t sz, void *arg) 57241675Suqs{ 58241675Suqs 59241675Suqs return(term_len((const struct termp *)arg, sz)); 60241675Suqs} 61241675Suqs 62241675Suqsvoid 63241675Suqsterm_tbl(struct termp *tp, const struct tbl_span *sp) 64241675Suqs{ 65241675Suqs const struct tbl_head *hp; 66241675Suqs const struct tbl_dat *dp; 67241675Suqs struct roffcol *col; 68241675Suqs int spans; 69241675Suqs size_t rmargin, maxrmargin; 70241675Suqs 71241675Suqs rmargin = tp->rmargin; 72241675Suqs maxrmargin = tp->maxrmargin; 73241675Suqs 74241675Suqs tp->rmargin = tp->maxrmargin = TERM_MAXMARGIN; 75241675Suqs 76241675Suqs /* Inhibit printing of spaces: we do padding ourselves. */ 77241675Suqs 78241675Suqs tp->flags |= TERMP_NONOSPACE; 79241675Suqs tp->flags |= TERMP_NOSPACE; 80241675Suqs 81241675Suqs /* 82241675Suqs * The first time we're invoked for a given table block, 83241675Suqs * calculate the table widths and decimal positions. 84241675Suqs */ 85241675Suqs 86241675Suqs if (TBL_SPAN_FIRST & sp->flags) { 87241675Suqs term_flushln(tp); 88241675Suqs 89241675Suqs tp->tbl.len = term_tbl_len; 90241675Suqs tp->tbl.slen = term_tbl_strlen; 91241675Suqs tp->tbl.arg = tp; 92241675Suqs 93241675Suqs tblcalc(&tp->tbl, sp); 94241675Suqs } 95241675Suqs 96241675Suqs /* Horizontal frame at the start of boxed tables. */ 97241675Suqs 98241675Suqs if (TBL_SPAN_FIRST & sp->flags) { 99241675Suqs if (TBL_OPT_DBOX & sp->tbl->opts) 100241675Suqs tbl_hframe(tp, sp, 1); 101241675Suqs if (TBL_OPT_DBOX & sp->tbl->opts || 102241675Suqs TBL_OPT_BOX & sp->tbl->opts) 103241675Suqs tbl_hframe(tp, sp, 0); 104241675Suqs } 105241675Suqs 106241675Suqs /* Vertical frame at the start of each row. */ 107241675Suqs 108241675Suqs if (TBL_OPT_BOX & sp->tbl->opts || TBL_OPT_DBOX & sp->tbl->opts) 109241675Suqs term_word(tp, TBL_SPAN_HORIZ == sp->pos || 110241675Suqs TBL_SPAN_DHORIZ == sp->pos ? "+" : "|"); 111241675Suqs 112241675Suqs /* 113241675Suqs * Now print the actual data itself depending on the span type. 114241675Suqs * Spanner spans get a horizontal rule; data spanners have their 115241675Suqs * data printed by matching data to header. 116241675Suqs */ 117241675Suqs 118241675Suqs switch (sp->pos) { 119241675Suqs case (TBL_SPAN_HORIZ): 120241675Suqs /* FALLTHROUGH */ 121241675Suqs case (TBL_SPAN_DHORIZ): 122241675Suqs tbl_hrule(tp, sp); 123241675Suqs break; 124241675Suqs case (TBL_SPAN_DATA): 125241675Suqs /* Iterate over template headers. */ 126241675Suqs dp = sp->first; 127241675Suqs spans = 0; 128241675Suqs for (hp = sp->head; hp; hp = hp->next) { 129241675Suqs /* 130241675Suqs * If the current data header is invoked during 131241675Suqs * a spanner ("spans" > 0), don't emit anything 132241675Suqs * at all. 133241675Suqs */ 134241675Suqs switch (hp->pos) { 135241675Suqs case (TBL_HEAD_VERT): 136241675Suqs /* FALLTHROUGH */ 137241675Suqs case (TBL_HEAD_DVERT): 138241675Suqs if (spans <= 0) 139241675Suqs tbl_vrule(tp, hp); 140241675Suqs continue; 141241675Suqs case (TBL_HEAD_DATA): 142241675Suqs break; 143241675Suqs } 144241675Suqs 145241675Suqs if (--spans >= 0) 146241675Suqs continue; 147241675Suqs 148241675Suqs /* 149241675Suqs * All cells get a leading blank, except the 150241675Suqs * first one and those after double rulers. 151241675Suqs */ 152241675Suqs 153241675Suqs if (hp->prev && TBL_HEAD_DVERT != hp->prev->pos) 154241675Suqs tbl_char(tp, ASCII_NBRSP, 1); 155241675Suqs 156241675Suqs col = &tp->tbl.cols[hp->ident]; 157241675Suqs tbl_data(tp, sp->tbl, dp, col); 158241675Suqs 159241675Suqs /* No trailing blanks. */ 160241675Suqs 161241675Suqs if (NULL == hp->next) 162241675Suqs break; 163241675Suqs 164241675Suqs /* 165241675Suqs * Add another blank between cells, 166241675Suqs * or two when there is no vertical ruler. 167241675Suqs */ 168241675Suqs 169241675Suqs tbl_char(tp, ASCII_NBRSP, 170241675Suqs TBL_HEAD_VERT == hp->next->pos || 171241675Suqs TBL_HEAD_DVERT == hp->next->pos ? 1 : 2); 172241675Suqs 173241675Suqs /* 174241675Suqs * Go to the next data cell and assign the 175241675Suqs * number of subsequent spans, if applicable. 176241675Suqs */ 177241675Suqs 178241675Suqs if (dp) { 179241675Suqs spans = dp->spans; 180241675Suqs dp = dp->next; 181241675Suqs } 182241675Suqs } 183241675Suqs break; 184241675Suqs } 185241675Suqs 186241675Suqs /* Vertical frame at the end of each row. */ 187241675Suqs 188241675Suqs if (TBL_OPT_BOX & sp->tbl->opts || TBL_OPT_DBOX & sp->tbl->opts) 189241675Suqs term_word(tp, TBL_SPAN_HORIZ == sp->pos || 190241675Suqs TBL_SPAN_DHORIZ == sp->pos ? "+" : " |"); 191241675Suqs term_flushln(tp); 192241675Suqs 193241675Suqs /* 194241675Suqs * If we're the last row, clean up after ourselves: clear the 195241675Suqs * existing table configuration and set it to NULL. 196241675Suqs */ 197241675Suqs 198241675Suqs if (TBL_SPAN_LAST & sp->flags) { 199241675Suqs if (TBL_OPT_DBOX & sp->tbl->opts || 200241675Suqs TBL_OPT_BOX & sp->tbl->opts) 201241675Suqs tbl_hframe(tp, sp, 0); 202241675Suqs if (TBL_OPT_DBOX & sp->tbl->opts) 203241675Suqs tbl_hframe(tp, sp, 1); 204241675Suqs assert(tp->tbl.cols); 205241675Suqs free(tp->tbl.cols); 206241675Suqs tp->tbl.cols = NULL; 207241675Suqs } 208241675Suqs 209241675Suqs tp->flags &= ~TERMP_NONOSPACE; 210241675Suqs tp->rmargin = rmargin; 211241675Suqs tp->maxrmargin = maxrmargin; 212241675Suqs 213241675Suqs} 214241675Suqs 215241675Suqs/* 216241675Suqs * Horizontal rules extend across the entire table. 217241675Suqs * Calculate the width by iterating over columns. 218241675Suqs */ 219241675Suqsstatic size_t 220241675Suqstbl_rulewidth(struct termp *tp, const struct tbl_head *hp) 221241675Suqs{ 222241675Suqs size_t width; 223241675Suqs 224241675Suqs width = tp->tbl.cols[hp->ident].width; 225241675Suqs if (TBL_HEAD_DATA == hp->pos) { 226241675Suqs /* Account for leading blanks. */ 227241675Suqs if (hp->prev && TBL_HEAD_DVERT != hp->prev->pos) 228241675Suqs width++; 229241675Suqs /* Account for trailing blanks. */ 230241675Suqs width++; 231241675Suqs if (hp->next && 232241675Suqs TBL_HEAD_VERT != hp->next->pos && 233241675Suqs TBL_HEAD_DVERT != hp->next->pos) 234241675Suqs width++; 235241675Suqs } 236241675Suqs return(width); 237241675Suqs} 238241675Suqs 239241675Suqs/* 240241675Suqs * Rules inside the table can be single or double 241241675Suqs * and have crossings with vertical rules marked with pluses. 242241675Suqs */ 243241675Suqsstatic void 244241675Suqstbl_hrule(struct termp *tp, const struct tbl_span *sp) 245241675Suqs{ 246241675Suqs const struct tbl_head *hp; 247241675Suqs char c; 248241675Suqs 249241675Suqs c = '-'; 250241675Suqs if (TBL_SPAN_DHORIZ == sp->pos) 251241675Suqs c = '='; 252241675Suqs 253241675Suqs for (hp = sp->head; hp; hp = hp->next) 254241675Suqs tbl_char(tp, 255241675Suqs TBL_HEAD_DATA == hp->pos ? c : '+', 256241675Suqs tbl_rulewidth(tp, hp)); 257241675Suqs} 258241675Suqs 259241675Suqs/* 260241675Suqs * Rules above and below the table are always single 261241675Suqs * and have an additional plus at the beginning and end. 262241675Suqs * For double frames, this function is called twice, 263241675Suqs * and the outer one does not have crossings. 264241675Suqs */ 265241675Suqsstatic void 266241675Suqstbl_hframe(struct termp *tp, const struct tbl_span *sp, int outer) 267241675Suqs{ 268241675Suqs const struct tbl_head *hp; 269241675Suqs 270241675Suqs term_word(tp, "+"); 271241675Suqs for (hp = sp->head; hp; hp = hp->next) 272241675Suqs tbl_char(tp, 273241675Suqs outer || TBL_HEAD_DATA == hp->pos ? '-' : '+', 274241675Suqs tbl_rulewidth(tp, hp)); 275241675Suqs term_word(tp, "+"); 276241675Suqs term_flushln(tp); 277241675Suqs} 278241675Suqs 279241675Suqsstatic void 280241675Suqstbl_data(struct termp *tp, const struct tbl *tbl, 281241675Suqs const struct tbl_dat *dp, 282241675Suqs const struct roffcol *col) 283241675Suqs{ 284241675Suqs 285241675Suqs if (NULL == dp) { 286241675Suqs tbl_char(tp, ASCII_NBRSP, col->width); 287241675Suqs return; 288241675Suqs } 289241675Suqs assert(dp->layout); 290241675Suqs 291241675Suqs switch (dp->pos) { 292241675Suqs case (TBL_DATA_NONE): 293241675Suqs tbl_char(tp, ASCII_NBRSP, col->width); 294241675Suqs return; 295241675Suqs case (TBL_DATA_HORIZ): 296241675Suqs /* FALLTHROUGH */ 297241675Suqs case (TBL_DATA_NHORIZ): 298241675Suqs tbl_char(tp, '-', col->width); 299241675Suqs return; 300241675Suqs case (TBL_DATA_NDHORIZ): 301241675Suqs /* FALLTHROUGH */ 302241675Suqs case (TBL_DATA_DHORIZ): 303241675Suqs tbl_char(tp, '=', col->width); 304241675Suqs return; 305241675Suqs default: 306241675Suqs break; 307241675Suqs } 308241675Suqs 309241675Suqs switch (dp->layout->pos) { 310241675Suqs case (TBL_CELL_HORIZ): 311241675Suqs tbl_char(tp, '-', col->width); 312241675Suqs break; 313241675Suqs case (TBL_CELL_DHORIZ): 314241675Suqs tbl_char(tp, '=', col->width); 315241675Suqs break; 316241675Suqs case (TBL_CELL_LONG): 317241675Suqs /* FALLTHROUGH */ 318241675Suqs case (TBL_CELL_CENTRE): 319241675Suqs /* FALLTHROUGH */ 320241675Suqs case (TBL_CELL_LEFT): 321241675Suqs /* FALLTHROUGH */ 322241675Suqs case (TBL_CELL_RIGHT): 323241675Suqs tbl_literal(tp, dp, col); 324241675Suqs break; 325241675Suqs case (TBL_CELL_NUMBER): 326241675Suqs tbl_number(tp, tbl, dp, col); 327241675Suqs break; 328241675Suqs case (TBL_CELL_DOWN): 329241675Suqs tbl_char(tp, ASCII_NBRSP, col->width); 330241675Suqs break; 331241675Suqs default: 332241675Suqs abort(); 333241675Suqs /* NOTREACHED */ 334241675Suqs } 335241675Suqs} 336241675Suqs 337241675Suqsstatic void 338241675Suqstbl_vrule(struct termp *tp, const struct tbl_head *hp) 339241675Suqs{ 340241675Suqs 341241675Suqs switch (hp->pos) { 342241675Suqs case (TBL_HEAD_VERT): 343241675Suqs term_word(tp, "|"); 344241675Suqs break; 345241675Suqs case (TBL_HEAD_DVERT): 346241675Suqs term_word(tp, "||"); 347241675Suqs break; 348241675Suqs default: 349241675Suqs break; 350241675Suqs } 351241675Suqs} 352241675Suqs 353241675Suqsstatic void 354241675Suqstbl_char(struct termp *tp, char c, size_t len) 355241675Suqs{ 356241675Suqs size_t i, sz; 357241675Suqs char cp[2]; 358241675Suqs 359241675Suqs cp[0] = c; 360241675Suqs cp[1] = '\0'; 361241675Suqs 362241675Suqs sz = term_strlen(tp, cp); 363241675Suqs 364241675Suqs for (i = 0; i < len; i += sz) 365241675Suqs term_word(tp, cp); 366241675Suqs} 367241675Suqs 368241675Suqsstatic void 369241675Suqstbl_literal(struct termp *tp, const struct tbl_dat *dp, 370241675Suqs const struct roffcol *col) 371241675Suqs{ 372241675Suqs size_t len, padl, padr; 373241675Suqs 374241675Suqs assert(dp->string); 375241675Suqs len = term_strlen(tp, dp->string); 376241675Suqs padr = col->width > len ? col->width - len : 0; 377241675Suqs padl = 0; 378241675Suqs 379241675Suqs switch (dp->layout->pos) { 380241675Suqs case (TBL_CELL_LONG): 381241675Suqs padl = term_len(tp, 1); 382241675Suqs padr = padr > padl ? padr - padl : 0; 383241675Suqs break; 384241675Suqs case (TBL_CELL_CENTRE): 385241675Suqs if (2 > padr) 386241675Suqs break; 387241675Suqs padl = padr / 2; 388241675Suqs padr -= padl; 389241675Suqs break; 390241675Suqs case (TBL_CELL_RIGHT): 391241675Suqs padl = padr; 392241675Suqs padr = 0; 393241675Suqs break; 394241675Suqs default: 395241675Suqs break; 396241675Suqs } 397241675Suqs 398241675Suqs tbl_char(tp, ASCII_NBRSP, padl); 399241675Suqs term_word(tp, dp->string); 400241675Suqs tbl_char(tp, ASCII_NBRSP, padr); 401241675Suqs} 402241675Suqs 403241675Suqsstatic void 404241675Suqstbl_number(struct termp *tp, const struct tbl *tbl, 405241675Suqs const struct tbl_dat *dp, 406241675Suqs const struct roffcol *col) 407241675Suqs{ 408241675Suqs char *cp; 409241675Suqs char buf[2]; 410241675Suqs size_t sz, psz, ssz, d, padl; 411241675Suqs int i; 412241675Suqs 413241675Suqs /* 414241675Suqs * See calc_data_number(). Left-pad by taking the offset of our 415241675Suqs * and the maximum decimal; right-pad by the remaining amount. 416241675Suqs */ 417241675Suqs 418241675Suqs assert(dp->string); 419241675Suqs 420241675Suqs sz = term_strlen(tp, dp->string); 421241675Suqs 422241675Suqs buf[0] = tbl->decimal; 423241675Suqs buf[1] = '\0'; 424241675Suqs 425241675Suqs psz = term_strlen(tp, buf); 426241675Suqs 427241675Suqs if (NULL != (cp = strrchr(dp->string, tbl->decimal))) { 428241675Suqs buf[1] = '\0'; 429241675Suqs for (ssz = 0, i = 0; cp != &dp->string[i]; i++) { 430241675Suqs buf[0] = dp->string[i]; 431241675Suqs ssz += term_strlen(tp, buf); 432241675Suqs } 433241675Suqs d = ssz + psz; 434241675Suqs } else 435241675Suqs d = sz + psz; 436241675Suqs 437241675Suqs padl = col->decimal - d; 438241675Suqs 439241675Suqs tbl_char(tp, ASCII_NBRSP, padl); 440241675Suqs term_word(tp, dp->string); 441241675Suqs if (col->width > sz + padl) 442241675Suqs tbl_char(tp, ASCII_NBRSP, col->width - sz - padl); 443241675Suqs} 444241675Suqs 445