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