1 /* $Id: tbl_term.c,v 1.19 2011/01/25 12:07:30 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@kth.se> 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 void tbl_hframe(struct termp *, const struct tbl_span *); 38 static void tbl_literal(struct termp *, const struct tbl_dat *, 39 const struct roffcol *); 40 static void tbl_number(struct termp *, const struct tbl *, 41 const struct tbl_dat *, 42 const struct roffcol *); 43 static void tbl_hrule(struct termp *, const struct tbl_span *); 44 static void tbl_vframe(struct termp *, const struct tbl *); 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 tbl_hframe(tp, sp); 100 101 /* Vertical frame at the start of each row. */ 102 103 tbl_vframe(tp, sp->tbl); 104 105 /* 106 * Now print the actual data itself depending on the span type. 107 * Spanner spans get a horizontal rule; data spanners have their 108 * data printed by matching data to header. 109 */ 110 111 switch (sp->pos) { 112 case (TBL_SPAN_HORIZ): 113 /* FALLTHROUGH */ 114 case (TBL_SPAN_DHORIZ): 115 tbl_hrule(tp, sp); 116 break; 117 case (TBL_SPAN_DATA): 118 /* Iterate over template headers. */ 119 dp = sp->first; 120 spans = 0; 121 for (hp = sp->head; hp; hp = hp->next) { 122 /* 123 * If the current data header is invoked during 124 * a spanner ("spans" > 0), don't emit anything 125 * at all. 126 */ 127 switch (hp->pos) { 128 case (TBL_HEAD_VERT): 129 /* FALLTHROUGH */ 130 case (TBL_HEAD_DVERT): 131 if (spans <= 0) 132 tbl_vrule(tp, hp); 133 continue; 134 case (TBL_HEAD_DATA): 135 break; 136 } 137 138 if (--spans >= 0) 139 continue; 140 141 col = &tp->tbl.cols[hp->ident]; 142 tbl_data(tp, sp->tbl, 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 tbl_vframe(tp, sp->tbl); 158 term_flushln(tp); 159 160 /* 161 * If we're the last row, clean up after ourselves: clear the 162 * existing table configuration and set it to NULL. 163 */ 164 165 if (TBL_SPAN_LAST & sp->flags) { 166 tbl_hframe(tp, sp); 167 assert(tp->tbl.cols); 168 free(tp->tbl.cols); 169 tp->tbl.cols = NULL; 170 } 171 172 tp->flags &= ~TERMP_NONOSPACE; 173 tp->rmargin = rmargin; 174 tp->maxrmargin = maxrmargin; 175 176 } 177 178 static void 179 tbl_hrule(struct termp *tp, const struct tbl_span *sp) 180 { 181 const struct tbl_head *hp; 182 char c; 183 size_t width; 184 185 /* 186 * An hrule extends across the entire table and is demarked by a 187 * standalone `_' or whatnot in lieu of a table row. Spanning 188 * headers are marked by a `+', as are table boundaries. 189 */ 190 191 c = '-'; 192 if (TBL_SPAN_DHORIZ == sp->pos) 193 c = '='; 194 195 /* FIXME: don't use `+' between data and a spanner! */ 196 197 for (hp = sp->head; hp; hp = hp->next) { 198 width = tp->tbl.cols[hp->ident].width; 199 switch (hp->pos) { 200 case (TBL_HEAD_DATA): 201 if (hp->next) 202 width += 2; 203 tbl_char(tp, c, width); 204 break; 205 case (TBL_HEAD_DVERT): 206 tbl_char(tp, '+', width); 207 /* FALLTHROUGH */ 208 case (TBL_HEAD_VERT): 209 tbl_char(tp, '+', width); 210 break; 211 default: 212 abort(); 213 /* NOTREACHED */ 214 } 215 } 216 } 217 218 static void 219 tbl_hframe(struct termp *tp, const struct tbl_span *sp) 220 { 221 const struct tbl_head *hp; 222 size_t width; 223 224 if ( ! (TBL_OPT_BOX & sp->tbl->opts || 225 TBL_OPT_DBOX & sp->tbl->opts)) 226 return; 227 228 /* 229 * Print out the horizontal part of a frame or double frame. A 230 * double frame has an unbroken `-' outer line the width of the 231 * table, bordered by `+'. The frame (or inner frame, in the 232 * case of the double frame) is a `-' bordered by `+' and broken 233 * by `+' whenever a span is encountered. 234 */ 235 236 if (TBL_OPT_DBOX & sp->tbl->opts) { 237 term_word(tp, "+"); 238 for (hp = sp->head; hp; hp = hp->next) { 239 width = tp->tbl.cols[hp->ident].width; 240 tbl_char(tp, '-', width); 241 } 242 term_word(tp, "+"); 243 term_flushln(tp); 244 } 245 246 term_word(tp, "+"); 247 for (hp = sp->head; hp; hp = hp->next) { 248 width = tp->tbl.cols[hp->ident].width; 249 switch (hp->pos) { 250 case (TBL_HEAD_DATA): 251 tbl_char(tp, '-', width); 252 break; 253 default: 254 tbl_char(tp, '+', width); 255 break; 256 } 257 } 258 term_word(tp, "+"); 259 term_flushln(tp); 260 } 261 262 static void 263 tbl_data(struct termp *tp, const struct tbl *tbl, 264 const struct tbl_dat *dp, 265 const struct roffcol *col) 266 { 267 268 if (NULL == dp) { 269 tbl_char(tp, ASCII_NBRSP, col->width); 270 return; 271 } 272 assert(dp->layout); 273 274 switch (dp->pos) { 275 case (TBL_DATA_NONE): 276 tbl_char(tp, ASCII_NBRSP, col->width); 277 return; 278 case (TBL_DATA_HORIZ): 279 /* FALLTHROUGH */ 280 case (TBL_DATA_NHORIZ): 281 tbl_char(tp, '-', col->width); 282 return; 283 case (TBL_DATA_NDHORIZ): 284 /* FALLTHROUGH */ 285 case (TBL_DATA_DHORIZ): 286 tbl_char(tp, '=', col->width); 287 return; 288 default: 289 break; 290 } 291 292 switch (dp->layout->pos) { 293 case (TBL_CELL_HORIZ): 294 tbl_char(tp, '-', col->width); 295 break; 296 case (TBL_CELL_DHORIZ): 297 tbl_char(tp, '=', col->width); 298 break; 299 case (TBL_CELL_LONG): 300 /* FALLTHROUGH */ 301 case (TBL_CELL_CENTRE): 302 /* FALLTHROUGH */ 303 case (TBL_CELL_LEFT): 304 /* FALLTHROUGH */ 305 case (TBL_CELL_RIGHT): 306 tbl_literal(tp, dp, col); 307 break; 308 case (TBL_CELL_NUMBER): 309 tbl_number(tp, tbl, dp, col); 310 break; 311 case (TBL_CELL_DOWN): 312 tbl_char(tp, ASCII_NBRSP, col->width); 313 break; 314 default: 315 abort(); 316 /* NOTREACHED */ 317 } 318 } 319 320 static void 321 tbl_vrule(struct termp *tp, const struct tbl_head *hp) 322 { 323 324 switch (hp->pos) { 325 case (TBL_HEAD_VERT): 326 term_word(tp, "|"); 327 break; 328 case (TBL_HEAD_DVERT): 329 term_word(tp, "||"); 330 break; 331 default: 332 break; 333 } 334 } 335 336 static void 337 tbl_vframe(struct termp *tp, const struct tbl *tbl) 338 { 339 340 if (TBL_OPT_BOX & tbl->opts || TBL_OPT_DBOX & tbl->opts) 341 term_word(tp, "|"); 342 } 343 344 static void 345 tbl_char(struct termp *tp, char c, size_t len) 346 { 347 size_t i, sz; 348 char cp[2]; 349 350 cp[0] = c; 351 cp[1] = '\0'; 352 353 sz = term_strlen(tp, cp); 354 355 for (i = 0; i < len; i += sz) 356 term_word(tp, cp); 357 } 358 359 static void 360 tbl_literal(struct termp *tp, const struct tbl_dat *dp, 361 const struct roffcol *col) 362 { 363 size_t padl, padr, ssz; 364 365 padl = padr = 0; 366 367 assert(dp->string); 368 369 ssz = term_len(tp, 1); 370 371 switch (dp->layout->pos) { 372 case (TBL_CELL_LONG): 373 padl = ssz; 374 padr = col->width - term_strlen(tp, dp->string) - ssz; 375 break; 376 case (TBL_CELL_CENTRE): 377 padr = col->width - term_strlen(tp, dp->string); 378 if (3 > padr) 379 break; 380 padl = (padr - 1) / 2; 381 padr -= padl; 382 break; 383 case (TBL_CELL_RIGHT): 384 padl = col->width - term_strlen(tp, dp->string); 385 break; 386 default: 387 padr = col->width - term_strlen(tp, dp->string); 388 break; 389 } 390 391 tbl_char(tp, ASCII_NBRSP, padl); 392 term_word(tp, dp->string); 393 tbl_char(tp, ASCII_NBRSP, padr + 2); 394 } 395 396 static void 397 tbl_number(struct termp *tp, const struct tbl *tbl, 398 const struct tbl_dat *dp, 399 const struct roffcol *col) 400 { 401 char *cp; 402 char buf[2]; 403 size_t sz, psz, ssz, d, padl; 404 int i; 405 406 /* 407 * See calc_data_number(). Left-pad by taking the offset of our 408 * and the maximum decimal; right-pad by the remaining amount. 409 */ 410 411 assert(dp->string); 412 413 sz = term_strlen(tp, dp->string); 414 415 buf[0] = tbl->decimal; 416 buf[1] = '\0'; 417 418 psz = term_strlen(tp, buf); 419 420 if (NULL != (cp = strrchr(dp->string, tbl->decimal))) { 421 buf[1] = '\0'; 422 for (ssz = 0, i = 0; cp != &dp->string[i]; i++) { 423 buf[0] = dp->string[i]; 424 ssz += term_strlen(tp, buf); 425 } 426 d = ssz + psz; 427 } else 428 d = sz + psz; 429 430 sz += term_len(tp, 2); 431 d += term_len(tp, 1); 432 433 padl = col->decimal - d; 434 435 tbl_char(tp, ASCII_NBRSP, padl); 436 term_word(tp, dp->string); 437 tbl_char(tp, ASCII_NBRSP, col->width - sz - padl); 438 } 439 440