1 /* $Id: tbl_term.c,v 1.27 2014/04/20 16:46:05 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2011, 2012, 2014 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 | TBL_OPT_DBOX) & sp->opts->opts || 109 sp->head->vert) 110 term_word(tp, TBL_SPAN_HORIZ == sp->pos || 111 TBL_SPAN_DHORIZ == sp->pos ? "+" : "|"); 112 113 /* 114 * Now print the actual data itself depending on the span type. 115 * Spanner spans get a horizontal rule; data spanners have their 116 * data printed by matching data to header. 117 */ 118 119 switch (sp->pos) { 120 case TBL_SPAN_HORIZ: 121 /* FALLTHROUGH */ 122 case TBL_SPAN_DHORIZ: 123 tbl_hrule(tp, sp); 124 break; 125 case TBL_SPAN_DATA: 126 /* Iterate over template headers. */ 127 dp = sp->first; 128 spans = 0; 129 for (hp = sp->head; hp; hp = hp->next) { 130 131 /* 132 * If the current data header is invoked during 133 * a spanner ("spans" > 0), don't emit anything 134 * at all. 135 */ 136 137 if (--spans >= 0) 138 continue; 139 140 /* Separate columns. */ 141 142 if (NULL != hp->prev) 143 tbl_vrule(tp, hp); 144 145 col = &tp->tbl.cols[hp->ident]; 146 tbl_data(tp, sp->opts, dp, col); 147 148 /* 149 * Go to the next data cell and assign the 150 * number of subsequent spans, if applicable. 151 */ 152 153 if (dp) { 154 spans = dp->spans; 155 dp = dp->next; 156 } 157 } 158 break; 159 } 160 161 /* Vertical frame at the end of each row. */ 162 163 if ((TBL_OPT_BOX | TBL_OPT_DBOX) & sp->opts->opts || 164 sp->layout->vert) 165 term_word(tp, TBL_SPAN_HORIZ == sp->pos || 166 TBL_SPAN_DHORIZ == sp->pos ? "+" : " |"); 167 term_flushln(tp); 168 169 /* 170 * If we're the last row, clean up after ourselves: clear the 171 * existing table configuration and set it to NULL. 172 */ 173 174 if (TBL_SPAN_LAST & sp->flags) { 175 if (TBL_OPT_DBOX & sp->opts->opts || 176 TBL_OPT_BOX & sp->opts->opts) { 177 tbl_hframe(tp, sp, 0); 178 tp->skipvsp = 1; 179 } 180 if (TBL_OPT_DBOX & sp->opts->opts) { 181 tbl_hframe(tp, sp, 1); 182 tp->skipvsp = 2; 183 } 184 assert(tp->tbl.cols); 185 free(tp->tbl.cols); 186 tp->tbl.cols = NULL; 187 } 188 189 tp->flags &= ~TERMP_NONOSPACE; 190 tp->rmargin = rmargin; 191 tp->maxrmargin = maxrmargin; 192 193 } 194 195 /* 196 * Horizontal rules extend across the entire table. 197 * Calculate the width by iterating over columns. 198 */ 199 static size_t 200 tbl_rulewidth(struct termp *tp, const struct tbl_head *hp) 201 { 202 size_t width; 203 204 width = tp->tbl.cols[hp->ident].width; 205 206 /* Account for leading blanks. */ 207 if (hp->prev) 208 width += 2 - hp->vert; 209 210 /* Account for trailing blank. */ 211 width++; 212 213 return(width); 214 } 215 216 /* 217 * Rules inside the table can be single or double 218 * and have crossings with vertical rules marked with pluses. 219 */ 220 static void 221 tbl_hrule(struct termp *tp, const struct tbl_span *sp) 222 { 223 const struct tbl_head *hp; 224 char c; 225 226 c = '-'; 227 if (TBL_SPAN_DHORIZ == sp->pos) 228 c = '='; 229 230 for (hp = sp->head; hp; hp = hp->next) { 231 if (hp->prev && hp->vert) 232 tbl_char(tp, '+', hp->vert); 233 tbl_char(tp, c, tbl_rulewidth(tp, hp)); 234 } 235 } 236 237 /* 238 * Rules above and below the table are always single 239 * and have an additional plus at the beginning and end. 240 * For double frames, this function is called twice, 241 * and the outer one does not have crossings. 242 */ 243 static void 244 tbl_hframe(struct termp *tp, const struct tbl_span *sp, int outer) 245 { 246 const struct tbl_head *hp; 247 248 term_word(tp, "+"); 249 for (hp = sp->head; hp; hp = hp->next) { 250 if (hp->prev && hp->vert) 251 tbl_char(tp, (outer ? '-' : '+'), hp->vert); 252 tbl_char(tp, '-', tbl_rulewidth(tp, hp)); 253 } 254 term_word(tp, "+"); 255 term_flushln(tp); 256 } 257 258 static void 259 tbl_data(struct termp *tp, const struct tbl_opts *opts, 260 const struct tbl_dat *dp, 261 const struct roffcol *col) 262 { 263 264 if (NULL == dp) { 265 tbl_char(tp, ASCII_NBRSP, col->width); 266 return; 267 } 268 assert(dp->layout); 269 270 switch (dp->pos) { 271 case TBL_DATA_NONE: 272 tbl_char(tp, ASCII_NBRSP, col->width); 273 return; 274 case TBL_DATA_HORIZ: 275 /* FALLTHROUGH */ 276 case TBL_DATA_NHORIZ: 277 tbl_char(tp, '-', col->width); 278 return; 279 case TBL_DATA_NDHORIZ: 280 /* FALLTHROUGH */ 281 case TBL_DATA_DHORIZ: 282 tbl_char(tp, '=', col->width); 283 return; 284 default: 285 break; 286 } 287 288 switch (dp->layout->pos) { 289 case TBL_CELL_HORIZ: 290 tbl_char(tp, '-', col->width); 291 break; 292 case TBL_CELL_DHORIZ: 293 tbl_char(tp, '=', col->width); 294 break; 295 case TBL_CELL_LONG: 296 /* FALLTHROUGH */ 297 case TBL_CELL_CENTRE: 298 /* FALLTHROUGH */ 299 case TBL_CELL_LEFT: 300 /* FALLTHROUGH */ 301 case TBL_CELL_RIGHT: 302 tbl_literal(tp, dp, col); 303 break; 304 case TBL_CELL_NUMBER: 305 tbl_number(tp, opts, dp, col); 306 break; 307 case TBL_CELL_DOWN: 308 tbl_char(tp, ASCII_NBRSP, col->width); 309 break; 310 default: 311 abort(); 312 /* NOTREACHED */ 313 } 314 } 315 316 static void 317 tbl_vrule(struct termp *tp, const struct tbl_head *hp) 318 { 319 320 tbl_char(tp, ASCII_NBRSP, 1); 321 if (0 < hp->vert) 322 tbl_char(tp, '|', hp->vert); 323 if (2 > hp->vert) 324 tbl_char(tp, ASCII_NBRSP, 2 - hp->vert); 325 } 326 327 static void 328 tbl_char(struct termp *tp, char c, size_t len) 329 { 330 size_t i, sz; 331 char cp[2]; 332 333 cp[0] = c; 334 cp[1] = '\0'; 335 336 sz = term_strlen(tp, cp); 337 338 for (i = 0; i < len; i += sz) 339 term_word(tp, cp); 340 } 341 342 static void 343 tbl_literal(struct termp *tp, const struct tbl_dat *dp, 344 const struct roffcol *col) 345 { 346 struct tbl_head *hp; 347 size_t width, len, padl, padr; 348 int spans; 349 350 assert(dp->string); 351 len = term_strlen(tp, dp->string); 352 353 hp = dp->layout->head->next; 354 width = col->width; 355 for (spans = dp->spans; spans--; hp = hp->next) 356 width += tp->tbl.cols[hp->ident].width + 3; 357 358 padr = width > len ? width - len : 0; 359 padl = 0; 360 361 switch (dp->layout->pos) { 362 case TBL_CELL_LONG: 363 padl = term_len(tp, 1); 364 padr = padr > padl ? padr - padl : 0; 365 break; 366 case TBL_CELL_CENTRE: 367 if (2 > padr) 368 break; 369 padl = padr / 2; 370 padr -= padl; 371 break; 372 case TBL_CELL_RIGHT: 373 padl = padr; 374 padr = 0; 375 break; 376 default: 377 break; 378 } 379 380 tbl_char(tp, ASCII_NBRSP, padl); 381 term_word(tp, dp->string); 382 tbl_char(tp, ASCII_NBRSP, padr); 383 } 384 385 static void 386 tbl_number(struct termp *tp, const struct tbl_opts *opts, 387 const struct tbl_dat *dp, 388 const struct roffcol *col) 389 { 390 char *cp; 391 char buf[2]; 392 size_t sz, psz, ssz, d, padl; 393 int i; 394 395 /* 396 * See calc_data_number(). Left-pad by taking the offset of our 397 * and the maximum decimal; right-pad by the remaining amount. 398 */ 399 400 assert(dp->string); 401 402 sz = term_strlen(tp, dp->string); 403 404 buf[0] = opts->decimal; 405 buf[1] = '\0'; 406 407 psz = term_strlen(tp, buf); 408 409 if (NULL != (cp = strrchr(dp->string, opts->decimal))) { 410 buf[1] = '\0'; 411 for (ssz = 0, i = 0; cp != &dp->string[i]; i++) { 412 buf[0] = dp->string[i]; 413 ssz += term_strlen(tp, buf); 414 } 415 d = ssz + psz; 416 } else 417 d = sz + psz; 418 419 padl = col->decimal - d; 420 421 tbl_char(tp, ASCII_NBRSP, padl); 422 term_word(tp, dp->string); 423 if (col->width > sz + padl) 424 tbl_char(tp, ASCII_NBRSP, col->width - sz - padl); 425 } 426 427