1 /* $Id: tbl_term.c,v 1.40 2015/03/06 15:48:53 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2011, 2012, 2014, 2015 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 "config.h" 19 20 #include <sys/types.h> 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 void tbl_literal(struct termp *, const struct tbl_dat *, 38 const struct roffcol *); 39 static void tbl_number(struct termp *, const struct tbl_opts *, 40 const struct tbl_dat *, 41 const struct roffcol *); 42 static void tbl_hrule(struct termp *, const struct tbl_span *, int); 43 static void tbl_word(struct termp *, const struct tbl_dat *); 44 45 46 static size_t 47 term_tbl_strlen(const char *p, void *arg) 48 { 49 50 return(term_strlen((const struct termp *)arg, p)); 51 } 52 53 static size_t 54 term_tbl_len(size_t sz, void *arg) 55 { 56 57 return(term_len((const struct termp *)arg, sz)); 58 } 59 60 void 61 term_tbl(struct termp *tp, const struct tbl_span *sp) 62 { 63 const struct tbl_cell *cp; 64 const struct tbl_dat *dp; 65 static size_t offset; 66 size_t rmargin, maxrmargin, tsz; 67 int ic, horiz, spans, vert; 68 69 rmargin = tp->rmargin; 70 maxrmargin = tp->maxrmargin; 71 72 tp->rmargin = tp->maxrmargin = TERM_MAXMARGIN; 73 74 /* Inhibit printing of spaces: we do padding ourselves. */ 75 76 tp->flags |= TERMP_NONOSPACE; 77 tp->flags |= TERMP_NOSPACE; 78 79 /* 80 * The first time we're invoked for a given table block, 81 * calculate the table widths and decimal positions. 82 */ 83 84 if (tp->tbl.cols == NULL) { 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, rmargin - tp->offset); 90 91 /* Center the table as a whole. */ 92 93 offset = tp->offset; 94 if (sp->opts->opts & TBL_OPT_CENTRE) { 95 tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) 96 ? 2 : !!sp->opts->lvert + !!sp->opts->rvert; 97 for (ic = 0; ic < sp->opts->cols; ic++) 98 tsz += tp->tbl.cols[ic].width + 3; 99 tsz -= 3; 100 if (offset + tsz > rmargin) 101 tsz -= 1; 102 tp->offset = (offset + rmargin > tsz) ? 103 (offset + rmargin - tsz) / 2 : 0; 104 } 105 106 /* Horizontal frame at the start of boxed tables. */ 107 108 if (sp->opts->opts & TBL_OPT_DBOX) 109 tbl_hrule(tp, sp, 2); 110 if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) 111 tbl_hrule(tp, sp, 1); 112 } 113 114 /* Vertical frame at the start of each row. */ 115 116 horiz = sp->pos == TBL_SPAN_HORIZ || sp->pos == TBL_SPAN_DHORIZ; 117 118 if (sp->layout->vert || 119 (sp->prev != NULL && sp->prev->layout->vert) || 120 sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)) 121 term_word(tp, horiz ? "+" : "|"); 122 else if (sp->opts->lvert) 123 tbl_char(tp, horiz ? '-' : ASCII_NBRSP, 1); 124 125 /* 126 * Now print the actual data itself depending on the span type. 127 * Match data cells to column numbers. 128 */ 129 130 if (sp->pos == TBL_SPAN_DATA) { 131 cp = sp->layout->first; 132 dp = sp->first; 133 spans = 0; 134 for (ic = 0; ic < sp->opts->cols; ic++) { 135 136 /* 137 * Remeber whether we need a vertical bar 138 * after this cell. 139 */ 140 141 vert = cp == NULL ? 0 : cp->vert; 142 143 /* 144 * Print the data and advance to the next cell. 145 */ 146 147 if (spans == 0) { 148 tbl_data(tp, sp->opts, dp, tp->tbl.cols + ic); 149 if (dp != NULL) { 150 spans = dp->spans; 151 dp = dp->next; 152 } 153 } else 154 spans--; 155 if (cp != NULL) 156 cp = cp->next; 157 158 /* 159 * Separate columns, except in the middle 160 * of spans and after the last cell. 161 */ 162 163 if (ic + 1 == sp->opts->cols || spans) 164 continue; 165 166 tbl_char(tp, ASCII_NBRSP, 1); 167 if (vert > 0) 168 tbl_char(tp, '|', vert); 169 if (vert < 2) 170 tbl_char(tp, ASCII_NBRSP, 2 - vert); 171 } 172 } else if (horiz) 173 tbl_hrule(tp, sp, 0); 174 175 /* Vertical frame at the end of each row. */ 176 177 if (sp->layout->last->vert || 178 (sp->prev != NULL && sp->prev->layout->last->vert) || 179 (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX))) 180 term_word(tp, horiz ? "+" : " |"); 181 else if (sp->opts->rvert) 182 tbl_char(tp, horiz ? '-' : ASCII_NBRSP, 1); 183 term_flushln(tp); 184 185 /* 186 * If we're the last row, clean up after ourselves: clear the 187 * existing table configuration and set it to NULL. 188 */ 189 190 if (sp->next == NULL) { 191 if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) { 192 tbl_hrule(tp, sp, 1); 193 tp->skipvsp = 1; 194 } 195 if (sp->opts->opts & TBL_OPT_DBOX) { 196 tbl_hrule(tp, sp, 2); 197 tp->skipvsp = 2; 198 } 199 assert(tp->tbl.cols); 200 free(tp->tbl.cols); 201 tp->tbl.cols = NULL; 202 tp->offset = offset; 203 } 204 205 tp->flags &= ~TERMP_NONOSPACE; 206 tp->rmargin = rmargin; 207 tp->maxrmargin = maxrmargin; 208 } 209 210 /* 211 * Kinds of horizontal rulers: 212 * 0: inside the table (single or double line with crossings) 213 * 1: inner frame (single line with crossings and ends) 214 * 2: outer frame (single line without crossings with ends) 215 */ 216 static void 217 tbl_hrule(struct termp *tp, const struct tbl_span *sp, int kind) 218 { 219 const struct tbl_cell *c1, *c2; 220 int vert; 221 char line, cross; 222 223 line = (kind == 0 && TBL_SPAN_DHORIZ == sp->pos) ? '=' : '-'; 224 cross = (kind < 2) ? '+' : '-'; 225 226 if (kind) 227 term_word(tp, "+"); 228 c1 = sp->layout->first; 229 c2 = sp->prev == NULL ? NULL : sp->prev->layout->first; 230 if (c2 == c1) 231 c2 = NULL; 232 for (;;) { 233 tbl_char(tp, line, tp->tbl.cols[c1->col].width + 1); 234 vert = c1->vert; 235 if ((c1 = c1->next) == NULL) 236 break; 237 if (c2 != NULL) { 238 if (vert < c2->vert) 239 vert = c2->vert; 240 c2 = c2->next; 241 } 242 if (vert) 243 tbl_char(tp, cross, vert); 244 if (vert < 2) 245 tbl_char(tp, line, 2 - vert); 246 } 247 if (kind) { 248 term_word(tp, "+"); 249 term_flushln(tp); 250 } 251 } 252 253 static void 254 tbl_data(struct termp *tp, const struct tbl_opts *opts, 255 const struct tbl_dat *dp, 256 const struct roffcol *col) 257 { 258 259 if (dp == NULL) { 260 tbl_char(tp, ASCII_NBRSP, col->width); 261 return; 262 } 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, opts, 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_char(struct termp *tp, char c, size_t len) 312 { 313 size_t i, sz; 314 char cp[2]; 315 316 cp[0] = c; 317 cp[1] = '\0'; 318 319 sz = term_strlen(tp, cp); 320 321 for (i = 0; i < len; i += sz) 322 term_word(tp, cp); 323 } 324 325 static void 326 tbl_literal(struct termp *tp, const struct tbl_dat *dp, 327 const struct roffcol *col) 328 { 329 size_t len, padl, padr, width; 330 int ic, spans; 331 332 assert(dp->string); 333 len = term_strlen(tp, dp->string); 334 width = col->width; 335 ic = dp->layout->col; 336 spans = dp->spans; 337 while (spans--) 338 width += tp->tbl.cols[++ic].width + 3; 339 340 padr = width > len ? width - len : 0; 341 padl = 0; 342 343 switch (dp->layout->pos) { 344 case TBL_CELL_LONG: 345 padl = term_len(tp, 1); 346 padr = padr > padl ? padr - padl : 0; 347 break; 348 case TBL_CELL_CENTRE: 349 if (2 > padr) 350 break; 351 padl = padr / 2; 352 padr -= padl; 353 break; 354 case TBL_CELL_RIGHT: 355 padl = padr; 356 padr = 0; 357 break; 358 default: 359 break; 360 } 361 362 tbl_char(tp, ASCII_NBRSP, padl); 363 tbl_word(tp, dp); 364 tbl_char(tp, ASCII_NBRSP, padr); 365 } 366 367 static void 368 tbl_number(struct termp *tp, const struct tbl_opts *opts, 369 const struct tbl_dat *dp, 370 const struct roffcol *col) 371 { 372 char *cp; 373 char buf[2]; 374 size_t sz, psz, ssz, d, padl; 375 int i; 376 377 /* 378 * See calc_data_number(). Left-pad by taking the offset of our 379 * and the maximum decimal; right-pad by the remaining amount. 380 */ 381 382 assert(dp->string); 383 384 sz = term_strlen(tp, dp->string); 385 386 buf[0] = opts->decimal; 387 buf[1] = '\0'; 388 389 psz = term_strlen(tp, buf); 390 391 if ((cp = strrchr(dp->string, opts->decimal)) != NULL) { 392 for (ssz = 0, i = 0; cp != &dp->string[i]; i++) { 393 buf[0] = dp->string[i]; 394 ssz += term_strlen(tp, buf); 395 } 396 d = ssz + psz; 397 } else 398 d = sz + psz; 399 400 if (col->decimal > d && col->width > sz) { 401 padl = col->decimal - d; 402 if (padl + sz > col->width) 403 padl = col->width - sz; 404 tbl_char(tp, ASCII_NBRSP, padl); 405 } else 406 padl = 0; 407 tbl_word(tp, dp); 408 if (col->width > sz + padl) 409 tbl_char(tp, ASCII_NBRSP, col->width - sz - padl); 410 } 411 412 static void 413 tbl_word(struct termp *tp, const struct tbl_dat *dp) 414 { 415 int prev_font; 416 417 prev_font = tp->fonti; 418 if (dp->layout->flags & TBL_CELL_BOLD) 419 term_fontpush(tp, TERMFONT_BOLD); 420 else if (dp->layout->flags & TBL_CELL_ITALIC) 421 term_fontpush(tp, TERMFONT_UNDER); 422 423 term_word(tp, dp->string); 424 425 term_fontpopq(tp, prev_font); 426 } 427