1 /*      $Id: tbl_term.c,v 1.25 2013/05/31 21:37:17 schwarze Exp $ */
   2 /*
   3  * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
   4  * Copyright (c) 2011, 2012 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_opts *,
  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_opts *, 
  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->opts->opts)
 100                         tbl_hframe(tp, sp, 1);
 101                 if (TBL_OPT_DBOX & sp->opts->opts ||
 102                     TBL_OPT_BOX  & sp->opts->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->opts->opts || TBL_OPT_DBOX & sp->opts->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                         /* 
 131                          * If the current data header is invoked during
 132                          * a spanner ("spans" > 0), don't emit anything
 133                          * at all.
 134                          */
 135 
 136                         if (--spans >= 0)
 137                                 continue;
 138 
 139                         /* Separate columns. */
 140 
 141                         if (NULL != hp->prev)
 142                                 tbl_vrule(tp, hp);
 143 
 144                         col = &tp->tbl.cols[hp->ident];
 145                         tbl_data(tp, sp->opts, dp, col);
 146 
 147                         /* 
 148                          * Go to the next data cell and assign the
 149                          * number of subsequent spans, if applicable.
 150                          */
 151 
 152                         if (dp) {
 153                                 spans = dp->spans;
 154                                 dp = dp->next;
 155                         }
 156                 }
 157                 break;
 158         }
 159 
 160         /* Vertical frame at the end of each row. */
 161 
 162         if (TBL_OPT_BOX & sp->opts->opts || TBL_OPT_DBOX & sp->opts->opts)
 163                 term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
 164                         TBL_SPAN_DHORIZ == sp->pos ? "+" : " |");
 165         term_flushln(tp);
 166 
 167         /*
 168          * If we're the last row, clean up after ourselves: clear the
 169          * existing table configuration and set it to NULL.
 170          */
 171 
 172         if (TBL_SPAN_LAST & sp->flags) {
 173                 if (TBL_OPT_DBOX & sp->opts->opts ||
 174                     TBL_OPT_BOX  & sp->opts->opts) {
 175                         tbl_hframe(tp, sp, 0);
 176                         tp->skipvsp = 1;
 177                 }
 178                 if (TBL_OPT_DBOX & sp->opts->opts) {
 179                         tbl_hframe(tp, sp, 1);
 180                         tp->skipvsp = 2;
 181                 }
 182                 assert(tp->tbl.cols);
 183                 free(tp->tbl.cols);
 184                 tp->tbl.cols = NULL;
 185         }
 186 
 187         tp->flags &= ~TERMP_NONOSPACE;
 188         tp->rmargin = rmargin;
 189         tp->maxrmargin = maxrmargin;
 190 
 191 }
 192 
 193 /*
 194  * Horizontal rules extend across the entire table.
 195  * Calculate the width by iterating over columns.
 196  */
 197 static size_t
 198 tbl_rulewidth(struct termp *tp, const struct tbl_head *hp)
 199 {
 200         size_t           width;
 201 
 202         width = tp->tbl.cols[hp->ident].width;
 203 
 204         /* Account for leading blanks. */
 205         if (hp->prev)
 206                 width += 2 - hp->vert;
 207 
 208         /* Account for trailing blank. */
 209         width++;
 210 
 211         return(width);
 212 }
 213 
 214 /*
 215  * Rules inside the table can be single or double
 216  * and have crossings with vertical rules marked with pluses.
 217  */
 218 static void
 219 tbl_hrule(struct termp *tp, const struct tbl_span *sp)
 220 {
 221         const struct tbl_head *hp;
 222         char             c;
 223 
 224         c = '-';
 225         if (TBL_SPAN_DHORIZ == sp->pos)
 226                 c = '=';
 227 
 228         for (hp = sp->head; hp; hp = hp->next) {
 229                 if (hp->prev && hp->vert)
 230                         tbl_char(tp, '+', hp->vert);
 231                 tbl_char(tp, c, tbl_rulewidth(tp, hp));
 232         }
 233 }
 234 
 235 /*
 236  * Rules above and below the table are always single
 237  * and have an additional plus at the beginning and end.
 238  * For double frames, this function is called twice,
 239  * and the outer one does not have crossings.
 240  */
 241 static void
 242 tbl_hframe(struct termp *tp, const struct tbl_span *sp, int outer)
 243 {
 244         const struct tbl_head *hp;
 245 
 246         term_word(tp, "+");
 247         for (hp = sp->head; hp; hp = hp->next) {
 248                 if (hp->prev && hp->vert)
 249                         tbl_char(tp, (outer ? '-' : '+'), hp->vert);
 250                 tbl_char(tp, '-', tbl_rulewidth(tp, hp));
 251         }
 252         term_word(tp, "+");
 253         term_flushln(tp);
 254 }
 255 
 256 static void
 257 tbl_data(struct termp *tp, const struct tbl_opts *opts,
 258                 const struct tbl_dat *dp, 
 259                 const struct roffcol *col)
 260 {
 261 
 262         if (NULL == dp) {
 263                 tbl_char(tp, ASCII_NBRSP, col->width);
 264                 return;
 265         }
 266         assert(dp->layout);
 267 
 268         switch (dp->pos) {
 269         case (TBL_DATA_NONE):
 270                 tbl_char(tp, ASCII_NBRSP, col->width);
 271                 return;
 272         case (TBL_DATA_HORIZ):
 273                 /* FALLTHROUGH */
 274         case (TBL_DATA_NHORIZ):
 275                 tbl_char(tp, '-', col->width);
 276                 return;
 277         case (TBL_DATA_NDHORIZ):
 278                 /* FALLTHROUGH */
 279         case (TBL_DATA_DHORIZ):
 280                 tbl_char(tp, '=', col->width);
 281                 return;
 282         default:
 283                 break;
 284         }
 285         
 286         switch (dp->layout->pos) {
 287         case (TBL_CELL_HORIZ):
 288                 tbl_char(tp, '-', col->width);
 289                 break;
 290         case (TBL_CELL_DHORIZ):
 291                 tbl_char(tp, '=', col->width);
 292                 break;
 293         case (TBL_CELL_LONG):
 294                 /* FALLTHROUGH */
 295         case (TBL_CELL_CENTRE):
 296                 /* FALLTHROUGH */
 297         case (TBL_CELL_LEFT):
 298                 /* FALLTHROUGH */
 299         case (TBL_CELL_RIGHT):
 300                 tbl_literal(tp, dp, col);
 301                 break;
 302         case (TBL_CELL_NUMBER):
 303                 tbl_number(tp, opts, dp, col);
 304                 break;
 305         case (TBL_CELL_DOWN):
 306                 tbl_char(tp, ASCII_NBRSP, col->width);
 307                 break;
 308         default:
 309                 abort();
 310                 /* NOTREACHED */
 311         }
 312 }
 313 
 314 static void
 315 tbl_vrule(struct termp *tp, const struct tbl_head *hp)
 316 {
 317 
 318         tbl_char(tp, ASCII_NBRSP, 1);
 319         if (0 < hp->vert)
 320                 tbl_char(tp, '|', hp->vert);
 321         if (2 > hp->vert)
 322                 tbl_char(tp, ASCII_NBRSP, 2 - hp->vert);
 323 }
 324 
 325 static void
 326 tbl_char(struct termp *tp, char c, size_t len)
 327 {
 328         size_t          i, sz;
 329         char            cp[2];
 330 
 331         cp[0] = c;
 332         cp[1] = '\0';
 333 
 334         sz = term_strlen(tp, cp);
 335 
 336         for (i = 0; i < len; i += sz)
 337                 term_word(tp, cp);
 338 }
 339 
 340 static void
 341 tbl_literal(struct termp *tp, const struct tbl_dat *dp, 
 342                 const struct roffcol *col)
 343 {
 344         struct tbl_head         *hp;
 345         size_t                   width, len, padl, padr;
 346         int                      spans;
 347 
 348         assert(dp->string);
 349         len = term_strlen(tp, dp->string);
 350 
 351         hp = dp->layout->head->next;
 352         width = col->width;
 353         for (spans = dp->spans; spans--; hp = hp->next)
 354                 width += tp->tbl.cols[hp->ident].width + 3;
 355 
 356         padr = width > len ? width - len : 0;
 357         padl = 0;
 358 
 359         switch (dp->layout->pos) {
 360         case (TBL_CELL_LONG):
 361                 padl = term_len(tp, 1);
 362                 padr = padr > padl ? padr - padl : 0;
 363                 break;
 364         case (TBL_CELL_CENTRE):
 365                 if (2 > padr)
 366                         break;
 367                 padl = padr / 2;
 368                 padr -= padl;
 369                 break;
 370         case (TBL_CELL_RIGHT):
 371                 padl = padr;
 372                 padr = 0;
 373                 break;
 374         default:
 375                 break;
 376         }
 377 
 378         tbl_char(tp, ASCII_NBRSP, padl);
 379         term_word(tp, dp->string);
 380         tbl_char(tp, ASCII_NBRSP, padr);
 381 }
 382 
 383 static void
 384 tbl_number(struct termp *tp, const struct tbl_opts *opts,
 385                 const struct tbl_dat *dp,
 386                 const struct roffcol *col)
 387 {
 388         char            *cp;
 389         char             buf[2];
 390         size_t           sz, psz, ssz, d, padl;
 391         int              i;
 392 
 393         /*
 394          * See calc_data_number().  Left-pad by taking the offset of our
 395          * and the maximum decimal; right-pad by the remaining amount.
 396          */
 397 
 398         assert(dp->string);
 399 
 400         sz = term_strlen(tp, dp->string);
 401 
 402         buf[0] = opts->decimal;
 403         buf[1] = '\0';
 404 
 405         psz = term_strlen(tp, buf);
 406 
 407         if (NULL != (cp = strrchr(dp->string, opts->decimal))) {
 408                 buf[1] = '\0';
 409                 for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
 410                         buf[0] = dp->string[i];
 411                         ssz += term_strlen(tp, buf);
 412                 }
 413                 d = ssz + psz;
 414         } else
 415                 d = sz + psz;
 416 
 417         padl = col->decimal - d;
 418 
 419         tbl_char(tp, ASCII_NBRSP, padl);
 420         term_word(tp, dp->string);
 421         if (col->width > sz + padl)
 422                 tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
 423 }
 424