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