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