1 /* $OpenBSD: tbl_term.c,v 1.28 2015/03/09 17:41:36 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 <sys/types.h> 19 20 #include <assert.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <string.h> 24 25 #include "mandoc.h" 26 #include "out.h" 27 #include "term.h" 28 29 static size_t term_tbl_len(size_t, void *); 30 static size_t term_tbl_strlen(const char *, void *); 31 static void tbl_char(struct termp *, char, size_t); 32 static void tbl_data(struct termp *, const struct tbl_opts *, 33 const struct tbl_dat *, 34 const struct roffcol *); 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 *, int); 41 static void tbl_word(struct termp *, const struct tbl_dat *); 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_cell *cp; 62 const struct tbl_dat *dp; 63 static size_t offset; 64 size_t rmargin, maxrmargin, tsz; 65 int ic, horiz, spans, vert; 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 (tp->tbl.cols == NULL) { 83 tp->tbl.len = term_tbl_len; 84 tp->tbl.slen = term_tbl_strlen; 85 tp->tbl.arg = tp; 86 87 tblcalc(&tp->tbl, sp, rmargin - tp->offset); 88 89 /* Center the table as a whole. */ 90 91 offset = tp->offset; 92 if (sp->opts->opts & TBL_OPT_CENTRE) { 93 tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) 94 ? 2 : !!sp->opts->lvert + !!sp->opts->rvert; 95 for (ic = 0; ic < sp->opts->cols; ic++) 96 tsz += tp->tbl.cols[ic].width + 3; 97 tsz -= 3; 98 if (offset + tsz > rmargin) 99 tsz -= 1; 100 tp->offset = (offset + rmargin > tsz) ? 101 (offset + rmargin - tsz) / 2 : 0; 102 } 103 104 /* Horizontal frame at the start of boxed tables. */ 105 106 if (sp->opts->opts & TBL_OPT_DBOX) 107 tbl_hrule(tp, sp, 2); 108 if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) 109 tbl_hrule(tp, sp, 1); 110 } 111 112 /* Vertical frame at the start of each row. */ 113 114 horiz = sp->pos == TBL_SPAN_HORIZ || sp->pos == TBL_SPAN_DHORIZ; 115 116 if (sp->layout->vert || 117 (sp->prev != NULL && sp->prev->layout->vert) || 118 sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)) 119 term_word(tp, horiz ? "+" : "|"); 120 else if (sp->opts->lvert) 121 tbl_char(tp, horiz ? '-' : ASCII_NBRSP, 1); 122 123 /* 124 * Now print the actual data itself depending on the span type. 125 * Match data cells to column numbers. 126 */ 127 128 if (sp->pos == TBL_SPAN_DATA) { 129 cp = sp->layout->first; 130 dp = sp->first; 131 spans = 0; 132 for (ic = 0; ic < sp->opts->cols; ic++) { 133 134 /* 135 * Remeber whether we need a vertical bar 136 * after this cell. 137 */ 138 139 vert = cp == NULL ? 0 : cp->vert; 140 141 /* 142 * Print the data and advance to the next cell. 143 */ 144 145 if (spans == 0) { 146 tbl_data(tp, sp->opts, dp, tp->tbl.cols + ic); 147 if (dp != NULL) { 148 spans = dp->spans; 149 dp = dp->next; 150 } 151 } else 152 spans--; 153 if (cp != NULL) 154 cp = cp->next; 155 156 /* 157 * Separate columns, except in the middle 158 * of spans and after the last cell. 159 */ 160 161 if (ic + 1 == sp->opts->cols || spans) 162 continue; 163 164 tbl_char(tp, ASCII_NBRSP, 1); 165 if (vert > 0) 166 tbl_char(tp, '|', vert); 167 if (vert < 2) 168 tbl_char(tp, ASCII_NBRSP, 2 - vert); 169 } 170 } else if (horiz) 171 tbl_hrule(tp, sp, 0); 172 173 /* Vertical frame at the end of each row. */ 174 175 if (sp->layout->last->vert || 176 (sp->prev != NULL && sp->prev->layout->last->vert) || 177 (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX))) 178 term_word(tp, horiz ? "+" : " |"); 179 else if (sp->opts->rvert) 180 tbl_char(tp, horiz ? '-' : ASCII_NBRSP, 1); 181 term_flushln(tp); 182 183 /* 184 * If we're the last row, clean up after ourselves: clear the 185 * existing table configuration and set it to NULL. 186 */ 187 188 if (sp->next == NULL) { 189 if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) { 190 tbl_hrule(tp, sp, 1); 191 tp->skipvsp = 1; 192 } 193 if (sp->opts->opts & TBL_OPT_DBOX) { 194 tbl_hrule(tp, sp, 2); 195 tp->skipvsp = 2; 196 } 197 assert(tp->tbl.cols); 198 free(tp->tbl.cols); 199 tp->tbl.cols = NULL; 200 tp->offset = offset; 201 } 202 203 tp->flags &= ~TERMP_NONOSPACE; 204 tp->rmargin = rmargin; 205 tp->maxrmargin = maxrmargin; 206 } 207 208 /* 209 * Kinds of horizontal rulers: 210 * 0: inside the table (single or double line with crossings) 211 * 1: inner frame (single line with crossings and ends) 212 * 2: outer frame (single line without crossings with ends) 213 */ 214 static void 215 tbl_hrule(struct termp *tp, const struct tbl_span *sp, int kind) 216 { 217 const struct tbl_cell *c1, *c2; 218 int vert; 219 char line, cross; 220 221 line = (kind == 0 && TBL_SPAN_DHORIZ == sp->pos) ? '=' : '-'; 222 cross = (kind < 2) ? '+' : '-'; 223 224 if (kind) 225 term_word(tp, "+"); 226 c1 = sp->layout->first; 227 c2 = sp->prev == NULL ? NULL : sp->prev->layout->first; 228 if (c2 == c1) 229 c2 = NULL; 230 for (;;) { 231 tbl_char(tp, line, tp->tbl.cols[c1->col].width + 1); 232 vert = c1->vert; 233 if ((c1 = c1->next) == NULL) 234 break; 235 if (c2 != NULL) { 236 if (vert < c2->vert) 237 vert = c2->vert; 238 c2 = c2->next; 239 } 240 if (vert) 241 tbl_char(tp, cross, vert); 242 if (vert < 2) 243 tbl_char(tp, line, 2 - vert); 244 } 245 if (kind) { 246 term_word(tp, "+"); 247 term_flushln(tp); 248 } 249 } 250 251 static void 252 tbl_data(struct termp *tp, const struct tbl_opts *opts, 253 const struct tbl_dat *dp, 254 const struct roffcol *col) 255 { 256 257 if (dp == NULL) { 258 tbl_char(tp, ASCII_NBRSP, col->width); 259 return; 260 } 261 262 switch (dp->pos) { 263 case TBL_DATA_NONE: 264 tbl_char(tp, ASCII_NBRSP, col->width); 265 return; 266 case TBL_DATA_HORIZ: 267 /* FALLTHROUGH */ 268 case TBL_DATA_NHORIZ: 269 tbl_char(tp, '-', col->width); 270 return; 271 case TBL_DATA_NDHORIZ: 272 /* FALLTHROUGH */ 273 case TBL_DATA_DHORIZ: 274 tbl_char(tp, '=', col->width); 275 return; 276 default: 277 break; 278 } 279 280 switch (dp->layout->pos) { 281 case TBL_CELL_HORIZ: 282 tbl_char(tp, '-', col->width); 283 break; 284 case TBL_CELL_DHORIZ: 285 tbl_char(tp, '=', col->width); 286 break; 287 case TBL_CELL_LONG: 288 /* FALLTHROUGH */ 289 case TBL_CELL_CENTRE: 290 /* FALLTHROUGH */ 291 case TBL_CELL_LEFT: 292 /* FALLTHROUGH */ 293 case TBL_CELL_RIGHT: 294 tbl_literal(tp, dp, col); 295 break; 296 case TBL_CELL_NUMBER: 297 tbl_number(tp, opts, dp, col); 298 break; 299 case TBL_CELL_DOWN: 300 tbl_char(tp, ASCII_NBRSP, col->width); 301 break; 302 default: 303 abort(); 304 /* NOTREACHED */ 305 } 306 } 307 308 static void 309 tbl_char(struct termp *tp, char c, size_t len) 310 { 311 size_t i, sz; 312 char cp[2]; 313 314 cp[0] = c; 315 cp[1] = '\0'; 316 317 sz = term_strlen(tp, cp); 318 319 for (i = 0; i < len; i += sz) 320 term_word(tp, cp); 321 } 322 323 static void 324 tbl_literal(struct termp *tp, const struct tbl_dat *dp, 325 const struct roffcol *col) 326 { 327 size_t len, padl, padr, width; 328 int ic, spans; 329 330 assert(dp->string); 331 len = term_strlen(tp, dp->string); 332 width = col->width; 333 ic = dp->layout->col; 334 spans = dp->spans; 335 while (spans--) 336 width += tp->tbl.cols[++ic].width + 3; 337 338 padr = width > len ? width - len : 0; 339 padl = 0; 340 341 switch (dp->layout->pos) { 342 case TBL_CELL_LONG: 343 padl = term_len(tp, 1); 344 padr = padr > padl ? padr - padl : 0; 345 break; 346 case TBL_CELL_CENTRE: 347 if (2 > padr) 348 break; 349 padl = padr / 2; 350 padr -= padl; 351 break; 352 case TBL_CELL_RIGHT: 353 padl = padr; 354 padr = 0; 355 break; 356 default: 357 break; 358 } 359 360 tbl_char(tp, ASCII_NBRSP, padl); 361 tbl_word(tp, dp); 362 tbl_char(tp, ASCII_NBRSP, padr); 363 } 364 365 static void 366 tbl_number(struct termp *tp, const struct tbl_opts *opts, 367 const struct tbl_dat *dp, 368 const struct roffcol *col) 369 { 370 char *cp; 371 char buf[2]; 372 size_t sz, psz, ssz, d, padl; 373 int i; 374 375 /* 376 * See calc_data_number(). Left-pad by taking the offset of our 377 * and the maximum decimal; right-pad by the remaining amount. 378 */ 379 380 assert(dp->string); 381 382 sz = term_strlen(tp, dp->string); 383 384 buf[0] = opts->decimal; 385 buf[1] = '\0'; 386 387 psz = term_strlen(tp, buf); 388 389 if ((cp = strrchr(dp->string, opts->decimal)) != NULL) { 390 for (ssz = 0, i = 0; cp != &dp->string[i]; i++) { 391 buf[0] = dp->string[i]; 392 ssz += term_strlen(tp, buf); 393 } 394 d = ssz + psz; 395 } else 396 d = sz + psz; 397 398 if (col->decimal > d && col->width > sz) { 399 padl = col->decimal - d; 400 if (padl + sz > col->width) 401 padl = col->width - sz; 402 tbl_char(tp, ASCII_NBRSP, padl); 403 } else 404 padl = 0; 405 tbl_word(tp, dp); 406 if (col->width > sz + padl) 407 tbl_char(tp, ASCII_NBRSP, col->width - sz - padl); 408 } 409 410 static void 411 tbl_word(struct termp *tp, const struct tbl_dat *dp) 412 { 413 int prev_font; 414 415 prev_font = tp->fonti; 416 if (dp->layout->flags & TBL_CELL_BOLD) 417 term_fontpush(tp, TERMFONT_BOLD); 418 else if (dp->layout->flags & TBL_CELL_ITALIC) 419 term_fontpush(tp, TERMFONT_UNDER); 420 421 term_word(tp, dp->string); 422 423 term_fontpopq(tp, prev_font); 424 } 425