1 /* $Id: tbl_term.c,v 1.43 2015/10/12 00:08:16 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 case TBL_DATA_NHORIZ: 270 tbl_char(tp, '-', col->width); 271 return; 272 case TBL_DATA_NDHORIZ: 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 case TBL_CELL_CENTRE: 289 case TBL_CELL_LEFT: 290 case TBL_CELL_RIGHT: 291 tbl_literal(tp, dp, col); 292 break; 293 case TBL_CELL_NUMBER: 294 tbl_number(tp, opts, dp, col); 295 break; 296 case TBL_CELL_DOWN: 297 tbl_char(tp, ASCII_NBRSP, col->width); 298 break; 299 default: 300 abort(); 301 } 302 } 303 304 static void 305 tbl_char(struct termp *tp, char c, size_t len) 306 { 307 size_t i, sz; 308 char cp[2]; 309 310 cp[0] = c; 311 cp[1] = '\0'; 312 313 sz = term_strlen(tp, cp); 314 315 for (i = 0; i < len; i += sz) 316 term_word(tp, cp); 317 } 318 319 static void 320 tbl_literal(struct termp *tp, const struct tbl_dat *dp, 321 const struct roffcol *col) 322 { 323 size_t len, padl, padr, width; 324 int ic, spans; 325 326 assert(dp->string); 327 len = term_strlen(tp, dp->string); 328 width = col->width; 329 ic = dp->layout->col; 330 spans = dp->spans; 331 while (spans--) 332 width += tp->tbl.cols[++ic].width + 3; 333 334 padr = width > len ? width - len : 0; 335 padl = 0; 336 337 switch (dp->layout->pos) { 338 case TBL_CELL_LONG: 339 padl = term_len(tp, 1); 340 padr = padr > padl ? padr - padl : 0; 341 break; 342 case TBL_CELL_CENTRE: 343 if (2 > padr) 344 break; 345 padl = padr / 2; 346 padr -= padl; 347 break; 348 case TBL_CELL_RIGHT: 349 padl = padr; 350 padr = 0; 351 break; 352 default: 353 break; 354 } 355 356 tbl_char(tp, ASCII_NBRSP, padl); 357 tbl_word(tp, dp); 358 tbl_char(tp, ASCII_NBRSP, padr); 359 } 360 361 static void 362 tbl_number(struct termp *tp, const struct tbl_opts *opts, 363 const struct tbl_dat *dp, 364 const struct roffcol *col) 365 { 366 char *cp; 367 char buf[2]; 368 size_t sz, psz, ssz, d, padl; 369 int i; 370 371 /* 372 * See calc_data_number(). Left-pad by taking the offset of our 373 * and the maximum decimal; right-pad by the remaining amount. 374 */ 375 376 assert(dp->string); 377 378 sz = term_strlen(tp, dp->string); 379 380 buf[0] = opts->decimal; 381 buf[1] = '\0'; 382 383 psz = term_strlen(tp, buf); 384 385 if ((cp = strrchr(dp->string, opts->decimal)) != NULL) { 386 for (ssz = 0, i = 0; cp != &dp->string[i]; i++) { 387 buf[0] = dp->string[i]; 388 ssz += term_strlen(tp, buf); 389 } 390 d = ssz + psz; 391 } else 392 d = sz + psz; 393 394 if (col->decimal > d && col->width > sz) { 395 padl = col->decimal - d; 396 if (padl + sz > col->width) 397 padl = col->width - sz; 398 tbl_char(tp, ASCII_NBRSP, padl); 399 } else 400 padl = 0; 401 tbl_word(tp, dp); 402 if (col->width > sz + padl) 403 tbl_char(tp, ASCII_NBRSP, col->width - sz - padl); 404 } 405 406 static void 407 tbl_word(struct termp *tp, const struct tbl_dat *dp) 408 { 409 int prev_font; 410 411 prev_font = tp->fonti; 412 if (dp->layout->flags & TBL_CELL_BOLD) 413 term_fontpush(tp, TERMFONT_BOLD); 414 else if (dp->layout->flags & TBL_CELL_ITALIC) 415 term_fontpush(tp, TERMFONT_UNDER); 416 417 term_word(tp, dp->string); 418 419 term_fontpopq(tp, prev_font); 420 } 421