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