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