1 /*      $Id: tbl_term.c,v 1.21 2011/09/20 23:05:49 schwarze Exp $ */
   2 /*
   3  * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
   4  * Copyright (c) 2011 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 #ifdef HAVE_CONFIG_H
  19 #include "config.h"
  20 #endif
  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 *,
  35                         const struct tbl_dat *, 
  36                         const struct roffcol *);
  37 static  size_t  tbl_rulewidth(struct termp *, const struct tbl_head *);
  38 static  void    tbl_hframe(struct termp *, const struct tbl_span *, int);
  39 static  void    tbl_literal(struct termp *, const struct tbl_dat *, 
  40                         const struct roffcol *);
  41 static  void    tbl_number(struct termp *, const struct tbl *, 
  42                         const struct tbl_dat *, 
  43                         const struct roffcol *);
  44 static  void    tbl_hrule(struct termp *, const struct tbl_span *);
  45 static  void    tbl_vrule(struct termp *, const struct tbl_head *);
  46 
  47 
  48 static size_t
  49 term_tbl_strlen(const char *p, void *arg)
  50 {
  51 
  52         return(term_strlen((const struct termp *)arg, p));
  53 }
  54 
  55 static size_t
  56 term_tbl_len(size_t sz, void *arg)
  57 {
  58 
  59         return(term_len((const struct termp *)arg, sz));
  60 }
  61 
  62 void
  63 term_tbl(struct termp *tp, const struct tbl_span *sp)
  64 {
  65         const struct tbl_head   *hp;
  66         const struct tbl_dat    *dp;
  67         struct roffcol          *col;
  68         int                      spans;
  69         size_t                   rmargin, maxrmargin;
  70 
  71         rmargin = tp->rmargin;
  72         maxrmargin = tp->maxrmargin;
  73 
  74         tp->rmargin = tp->maxrmargin = TERM_MAXMARGIN;
  75 
  76         /* Inhibit printing of spaces: we do padding ourselves. */
  77 
  78         tp->flags |= TERMP_NONOSPACE;
  79         tp->flags |= TERMP_NOSPACE;
  80 
  81         /*
  82          * The first time we're invoked for a given table block,
  83          * calculate the table widths and decimal positions.
  84          */
  85 
  86         if (TBL_SPAN_FIRST & sp->flags) {
  87                 term_flushln(tp);
  88 
  89                 tp->tbl.len = term_tbl_len;
  90                 tp->tbl.slen = term_tbl_strlen;
  91                 tp->tbl.arg = tp;
  92 
  93                 tblcalc(&tp->tbl, sp);
  94         }
  95 
  96         /* Horizontal frame at the start of boxed tables. */
  97 
  98         if (TBL_SPAN_FIRST & sp->flags) {
  99                 if (TBL_OPT_DBOX & sp->tbl->opts)
 100                         tbl_hframe(tp, sp, 1);
 101                 if (TBL_OPT_DBOX & sp->tbl->opts ||
 102                     TBL_OPT_BOX  & sp->tbl->opts)
 103                         tbl_hframe(tp, sp, 0);
 104         }
 105 
 106         /* Vertical frame at the start of each row. */
 107 
 108         if (TBL_OPT_BOX & sp->tbl->opts || TBL_OPT_DBOX & sp->tbl->opts)
 109                 term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
 110                         TBL_SPAN_DHORIZ == sp->pos ? "+" : "|");
 111 
 112         /*
 113          * Now print the actual data itself depending on the span type.
 114          * Spanner spans get a horizontal rule; data spanners have their
 115          * data printed by matching data to header.
 116          */
 117 
 118         switch (sp->pos) {
 119         case (TBL_SPAN_HORIZ):
 120                 /* FALLTHROUGH */
 121         case (TBL_SPAN_DHORIZ):
 122                 tbl_hrule(tp, sp);
 123                 break;
 124         case (TBL_SPAN_DATA):
 125                 /* Iterate over template headers. */
 126                 dp = sp->first;
 127                 spans = 0;
 128                 for (hp = sp->head; hp; hp = hp->next) {
 129                         /* 
 130                          * If the current data header is invoked during
 131                          * a spanner ("spans" > 0), don't emit anything
 132                          * at all.
 133                          */
 134                         switch (hp->pos) {
 135                         case (TBL_HEAD_VERT):
 136                                 /* FALLTHROUGH */
 137                         case (TBL_HEAD_DVERT):
 138                                 if (spans <= 0)
 139                                         tbl_vrule(tp, hp);
 140                                 continue;
 141                         case (TBL_HEAD_DATA):
 142                                 break;
 143                         }
 144 
 145                         if (--spans >= 0)
 146                                 continue;
 147 
 148                         /*
 149                          * All cells get a leading blank, except the
 150                          * first one and those after double rulers.
 151                          */
 152 
 153                         if (hp->prev && TBL_HEAD_DVERT != hp->prev->pos)
 154                                 tbl_char(tp, ASCII_NBRSP, 1);
 155 
 156                         col = &tp->tbl.cols[hp->ident];
 157                         tbl_data(tp, sp->tbl, dp, col);
 158 
 159                         /* No trailing blanks. */
 160 
 161                         if (NULL == hp->next)
 162                                 break;
 163 
 164                         /*
 165                          * Add another blank between cells,
 166                          * or two when there is no vertical ruler.
 167                          */
 168 
 169                         tbl_char(tp, ASCII_NBRSP,
 170                             TBL_HEAD_VERT  == hp->next->pos ||
 171                             TBL_HEAD_DVERT == hp->next->pos ? 1 : 2);
 172 
 173                         /* 
 174                          * Go to the next data cell and assign the
 175                          * number of subsequent spans, if applicable.
 176                          */
 177 
 178                         if (dp) {
 179                                 spans = dp->spans;
 180                                 dp = dp->next;
 181                         }
 182                 }
 183                 break;
 184         }
 185 
 186         /* Vertical frame at the end of each row. */
 187 
 188         if (TBL_OPT_BOX & sp->tbl->opts || TBL_OPT_DBOX & sp->tbl->opts)
 189                 term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
 190                         TBL_SPAN_DHORIZ == sp->pos ? "+" : " |");
 191         term_flushln(tp);
 192 
 193         /*
 194          * If we're the last row, clean up after ourselves: clear the
 195          * existing table configuration and set it to NULL.
 196          */
 197 
 198         if (TBL_SPAN_LAST & sp->flags) {
 199                 if (TBL_OPT_DBOX & sp->tbl->opts ||
 200                     TBL_OPT_BOX  & sp->tbl->opts)
 201                         tbl_hframe(tp, sp, 0);
 202                 if (TBL_OPT_DBOX & sp->tbl->opts)
 203                         tbl_hframe(tp, sp, 1);
 204                 assert(tp->tbl.cols);
 205                 free(tp->tbl.cols);
 206                 tp->tbl.cols = NULL;
 207         }
 208 
 209         tp->flags &= ~TERMP_NONOSPACE;
 210         tp->rmargin = rmargin;
 211         tp->maxrmargin = maxrmargin;
 212 
 213 }
 214 
 215 /*
 216  * Horizontal rules extend across the entire table.
 217  * Calculate the width by iterating over columns.
 218  */
 219 static size_t
 220 tbl_rulewidth(struct termp *tp, const struct tbl_head *hp)
 221 {
 222         size_t           width;
 223 
 224         width = tp->tbl.cols[hp->ident].width;
 225         if (TBL_HEAD_DATA == hp->pos) {
 226                 /* Account for leading blanks. */
 227                 if (hp->prev && TBL_HEAD_DVERT != hp->prev->pos)
 228                         width++;
 229                 /* Account for trailing blanks. */
 230                 width++;
 231                 if (hp->next &&
 232                     TBL_HEAD_VERT  != hp->next->pos &&
 233                     TBL_HEAD_DVERT != hp->next->pos)
 234                         width++;
 235         }
 236         return(width);
 237 }
 238 
 239 /*
 240  * Rules inside the table can be single or double
 241  * and have crossings with vertical rules marked with pluses.
 242  */
 243 static void
 244 tbl_hrule(struct termp *tp, const struct tbl_span *sp)
 245 {
 246         const struct tbl_head *hp;
 247         char             c;
 248 
 249         c = '-';
 250         if (TBL_SPAN_DHORIZ == sp->pos)
 251                 c = '=';
 252 
 253         for (hp = sp->head; hp; hp = hp->next)
 254                 tbl_char(tp,
 255                     TBL_HEAD_DATA == hp->pos ? c : '+',
 256                     tbl_rulewidth(tp, hp));
 257 }
 258 
 259 /*
 260  * Rules above and below the table are always single
 261  * and have an additional plus at the beginning and end.
 262  * For double frames, this function is called twice,
 263  * and the outer one does not have crossings.
 264  */
 265 static void
 266 tbl_hframe(struct termp *tp, const struct tbl_span *sp, int outer)
 267 {
 268         const struct tbl_head *hp;
 269 
 270         term_word(tp, "+");
 271         for (hp = sp->head; hp; hp = hp->next)
 272                 tbl_char(tp,
 273                     outer || TBL_HEAD_DATA == hp->pos ? '-' : '+',
 274                     tbl_rulewidth(tp, hp));
 275         term_word(tp, "+");
 276         term_flushln(tp);
 277 }
 278 
 279 static void
 280 tbl_data(struct termp *tp, const struct tbl *tbl,
 281                 const struct tbl_dat *dp, 
 282                 const struct roffcol *col)
 283 {
 284 
 285         if (NULL == dp) {
 286                 tbl_char(tp, ASCII_NBRSP, col->width);
 287                 return;
 288         }
 289         assert(dp->layout);
 290 
 291         switch (dp->pos) {
 292         case (TBL_DATA_NONE):
 293                 tbl_char(tp, ASCII_NBRSP, col->width);
 294                 return;
 295         case (TBL_DATA_HORIZ):
 296                 /* FALLTHROUGH */
 297         case (TBL_DATA_NHORIZ):
 298                 tbl_char(tp, '-', col->width);
 299                 return;
 300         case (TBL_DATA_NDHORIZ):
 301                 /* FALLTHROUGH */
 302         case (TBL_DATA_DHORIZ):
 303                 tbl_char(tp, '=', col->width);
 304                 return;
 305         default:
 306                 break;
 307         }
 308         
 309         switch (dp->layout->pos) {
 310         case (TBL_CELL_HORIZ):
 311                 tbl_char(tp, '-', col->width);
 312                 break;
 313         case (TBL_CELL_DHORIZ):
 314                 tbl_char(tp, '=', col->width);
 315                 break;
 316         case (TBL_CELL_LONG):
 317                 /* FALLTHROUGH */
 318         case (TBL_CELL_CENTRE):
 319                 /* FALLTHROUGH */
 320         case (TBL_CELL_LEFT):
 321                 /* FALLTHROUGH */
 322         case (TBL_CELL_RIGHT):
 323                 tbl_literal(tp, dp, col);
 324                 break;
 325         case (TBL_CELL_NUMBER):
 326                 tbl_number(tp, tbl, dp, col);
 327                 break;
 328         case (TBL_CELL_DOWN):
 329                 tbl_char(tp, ASCII_NBRSP, col->width);
 330                 break;
 331         default:
 332                 abort();
 333                 /* NOTREACHED */
 334         }
 335 }
 336 
 337 static void
 338 tbl_vrule(struct termp *tp, const struct tbl_head *hp)
 339 {
 340 
 341         switch (hp->pos) {
 342         case (TBL_HEAD_VERT):
 343                 term_word(tp, "|");
 344                 break;
 345         case (TBL_HEAD_DVERT):
 346                 term_word(tp, "||");
 347                 break;
 348         default:
 349                 break;
 350         }
 351 }
 352 
 353 static void
 354 tbl_char(struct termp *tp, char c, size_t len)
 355 {
 356         size_t          i, sz;
 357         char            cp[2];
 358 
 359         cp[0] = c;
 360         cp[1] = '\0';
 361 
 362         sz = term_strlen(tp, cp);
 363 
 364         for (i = 0; i < len; i += sz)
 365                 term_word(tp, cp);
 366 }
 367 
 368 static void
 369 tbl_literal(struct termp *tp, const struct tbl_dat *dp, 
 370                 const struct roffcol *col)
 371 {
 372         size_t           len, padl, padr;
 373 
 374         assert(dp->string);
 375         len = term_strlen(tp, dp->string);
 376         padr = col->width > len ? col->width - len : 0;
 377         padl = 0;
 378 
 379         switch (dp->layout->pos) {
 380         case (TBL_CELL_LONG):
 381                 padl = term_len(tp, 1);
 382                 padr = padr > padl ? padr - padl : 0;
 383                 break;
 384         case (TBL_CELL_CENTRE):
 385                 if (2 > padr)
 386                         break;
 387                 padl = padr / 2;
 388                 padr -= padl;
 389                 break;
 390         case (TBL_CELL_RIGHT):
 391                 padl = padr;
 392                 padr = 0;
 393                 break;
 394         default:
 395                 break;
 396         }
 397 
 398         tbl_char(tp, ASCII_NBRSP, padl);
 399         term_word(tp, dp->string);
 400         tbl_char(tp, ASCII_NBRSP, padr);
 401 }
 402 
 403 static void
 404 tbl_number(struct termp *tp, const struct tbl *tbl,
 405                 const struct tbl_dat *dp,
 406                 const struct roffcol *col)
 407 {
 408         char            *cp;
 409         char             buf[2];
 410         size_t           sz, psz, ssz, d, padl;
 411         int              i;
 412 
 413         /*
 414          * See calc_data_number().  Left-pad by taking the offset of our
 415          * and the maximum decimal; right-pad by the remaining amount.
 416          */
 417 
 418         assert(dp->string);
 419 
 420         sz = term_strlen(tp, dp->string);
 421 
 422         buf[0] = tbl->decimal;
 423         buf[1] = '\0';
 424 
 425         psz = term_strlen(tp, buf);
 426 
 427         if (NULL != (cp = strrchr(dp->string, tbl->decimal))) {
 428                 buf[1] = '\0';
 429                 for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
 430                         buf[0] = dp->string[i];
 431                         ssz += term_strlen(tp, buf);
 432                 }
 433                 d = ssz + psz;
 434         } else
 435                 d = sz + psz;
 436 
 437         padl = col->decimal - d;
 438 
 439         tbl_char(tp, ASCII_NBRSP, padl);
 440         term_word(tp, dp->string);
 441         if (col->width > sz + padl)
 442                 tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
 443 }
 444