1 /*      $Id: man_term.c,v 1.139 2013/12/22 23:34:13 schwarze Exp $ */
   2 /*
   3  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
   4  * Copyright (c) 2010, 2011, 2012, 2013 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 <sys/types.h>
  23 
  24 #include <assert.h>
  25 #include <ctype.h>
  26 #include <stdio.h>
  27 #include <stdlib.h>
  28 #include <string.h>
  29 
  30 #include "mandoc.h"
  31 #include "out.h"
  32 #include "man.h"
  33 #include "term.h"
  34 #include "main.h"
  35 
  36 #define MAXMARGINS        64 /* maximum number of indented scopes */
  37 
  38 struct  mtermp {
  39         int               fl;
  40 #define MANT_LITERAL     (1 << 0)
  41         size_t            lmargin[MAXMARGINS]; /* margins (incl. visible page) */
  42         int               lmargincur; /* index of current margin */
  43         int               lmarginsz; /* actual number of nested margins */
  44         size_t            offset; /* default offset to visible page */
  45         int               pardist; /* vert. space before par., unit: [v] */
  46 };
  47 
  48 #define DECL_ARGS         struct termp *p, \
  49                           struct mtermp *mt, \
  50                           const struct man_node *n, \
  51                           const struct man_meta *meta
  52 
  53 struct  termact {
  54         int             (*pre)(DECL_ARGS);
  55         void            (*post)(DECL_ARGS);
  56         int               flags;
  57 #define MAN_NOTEXT       (1 << 0) /* Never has text children. */
  58 };
  59 
  60 static  int               a2width(const struct termp *, const char *);
  61 static  size_t            a2height(const struct termp *, const char *);
  62 
  63 static  void              print_man_nodelist(DECL_ARGS);
  64 static  void              print_man_node(DECL_ARGS);
  65 static  void              print_man_head(struct termp *, const void *);
  66 static  void              print_man_foot(struct termp *, const void *);
  67 static  void              print_bvspace(struct termp *, 
  68                                 const struct man_node *, int);
  69 
  70 static  int               pre_B(DECL_ARGS);
  71 static  int               pre_HP(DECL_ARGS);
  72 static  int               pre_I(DECL_ARGS);
  73 static  int               pre_IP(DECL_ARGS);
  74 static  int               pre_OP(DECL_ARGS);
  75 static  int               pre_PD(DECL_ARGS);
  76 static  int               pre_PP(DECL_ARGS);
  77 static  int               pre_RS(DECL_ARGS);
  78 static  int               pre_SH(DECL_ARGS);
  79 static  int               pre_SS(DECL_ARGS);
  80 static  int               pre_TP(DECL_ARGS);
  81 static  int               pre_UR(DECL_ARGS);
  82 static  int               pre_alternate(DECL_ARGS);
  83 static  int               pre_ft(DECL_ARGS);
  84 static  int               pre_ign(DECL_ARGS);
  85 static  int               pre_in(DECL_ARGS);
  86 static  int               pre_literal(DECL_ARGS);
  87 static  int               pre_sp(DECL_ARGS);
  88 
  89 static  void              post_IP(DECL_ARGS);
  90 static  void              post_HP(DECL_ARGS);
  91 static  void              post_RS(DECL_ARGS);
  92 static  void              post_SH(DECL_ARGS);
  93 static  void              post_SS(DECL_ARGS);
  94 static  void              post_TP(DECL_ARGS);
  95 static  void              post_UR(DECL_ARGS);
  96 
  97 static  const struct termact termacts[MAN_MAX] = {
  98         { pre_sp, NULL, MAN_NOTEXT }, /* br */
  99         { NULL, NULL, 0 }, /* TH */
 100         { pre_SH, post_SH, 0 }, /* SH */
 101         { pre_SS, post_SS, 0 }, /* SS */
 102         { pre_TP, post_TP, 0 }, /* TP */
 103         { pre_PP, NULL, 0 }, /* LP */
 104         { pre_PP, NULL, 0 }, /* PP */
 105         { pre_PP, NULL, 0 }, /* P */
 106         { pre_IP, post_IP, 0 }, /* IP */
 107         { pre_HP, post_HP, 0 }, /* HP */ 
 108         { NULL, NULL, 0 }, /* SM */
 109         { pre_B, NULL, 0 }, /* SB */
 110         { pre_alternate, NULL, 0 }, /* BI */
 111         { pre_alternate, NULL, 0 }, /* IB */
 112         { pre_alternate, NULL, 0 }, /* BR */
 113         { pre_alternate, NULL, 0 }, /* RB */
 114         { NULL, NULL, 0 }, /* R */
 115         { pre_B, NULL, 0 }, /* B */
 116         { pre_I, NULL, 0 }, /* I */
 117         { pre_alternate, NULL, 0 }, /* IR */
 118         { pre_alternate, NULL, 0 }, /* RI */
 119         { pre_ign, NULL, MAN_NOTEXT }, /* na */
 120         { pre_sp, NULL, MAN_NOTEXT }, /* sp */
 121         { pre_literal, NULL, 0 }, /* nf */
 122         { pre_literal, NULL, 0 }, /* fi */
 123         { NULL, NULL, 0 }, /* RE */
 124         { pre_RS, post_RS, 0 }, /* RS */
 125         { pre_ign, NULL, 0 }, /* DT */
 126         { pre_ign, NULL, 0 }, /* UC */
 127         { pre_PD, NULL, MAN_NOTEXT }, /* PD */
 128         { pre_ign, NULL, 0 }, /* AT */
 129         { pre_in, NULL, MAN_NOTEXT }, /* in */
 130         { pre_ft, NULL, MAN_NOTEXT }, /* ft */
 131         { pre_OP, NULL, 0 }, /* OP */
 132         { pre_literal, NULL, 0 }, /* EX */
 133         { pre_literal, NULL, 0 }, /* EE */
 134         { pre_UR, post_UR, 0 }, /* UR */
 135         { NULL, NULL, 0 }, /* UE */
 136 };
 137 
 138 
 139 
 140 void
 141 terminal_man(void *arg, const struct man *man)
 142 {
 143         struct termp            *p;
 144         const struct man_node   *n;
 145         const struct man_meta   *meta;
 146         struct mtermp            mt;
 147 
 148         p = (struct termp *)arg;
 149 
 150         if (0 == p->defindent)
 151                 p->defindent = 7;
 152 
 153         p->overstep = 0;
 154         p->maxrmargin = p->defrmargin;
 155         p->tabwidth = term_len(p, 5);
 156 
 157         if (NULL == p->symtab)
 158                 p->symtab = mchars_alloc();
 159 
 160         n = man_node(man);
 161         meta = man_meta(man);
 162 
 163         term_begin(p, print_man_head, print_man_foot, meta);
 164         p->flags |= TERMP_NOSPACE;
 165 
 166         memset(&mt, 0, sizeof(struct mtermp));
 167 
 168         mt.lmargin[mt.lmargincur] = term_len(p, p->defindent);
 169         mt.offset = term_len(p, p->defindent);
 170         mt.pardist = 1;
 171 
 172         if (n->child)
 173                 print_man_nodelist(p, &mt, n->child, meta);
 174 
 175         term_end(p);
 176 }
 177 
 178 
 179 static size_t
 180 a2height(const struct termp *p, const char *cp)
 181 {
 182         struct roffsu    su;
 183 
 184         if ( ! a2roffsu(cp, &su, SCALE_VS))
 185                 SCALE_VS_INIT(&su, atoi(cp));
 186 
 187         return(term_vspan(p, &su));
 188 }
 189 
 190 
 191 static int
 192 a2width(const struct termp *p, const char *cp)
 193 {
 194         struct roffsu    su;
 195 
 196         if ( ! a2roffsu(cp, &su, SCALE_BU))
 197                 return(-1);
 198 
 199         return((int)term_hspan(p, &su));
 200 }
 201 
 202 /*
 203  * Printing leading vertical space before a block.
 204  * This is used for the paragraph macros.
 205  * The rules are pretty simple, since there's very little nesting going
 206  * on here.  Basically, if we're the first within another block (SS/SH),
 207  * then don't emit vertical space.  If we are (RS), then do.  If not the
 208  * first, print it.
 209  */
 210 static void
 211 print_bvspace(struct termp *p, const struct man_node *n, int pardist)
 212 {
 213         int      i;
 214 
 215         term_newln(p);
 216 
 217         if (n->body && n->body->child)
 218                 if (MAN_TBL == n->body->child->type)
 219                         return;
 220 
 221         if (MAN_ROOT == n->parent->type || MAN_RS != n->parent->tok)
 222                 if (NULL == n->prev)
 223                         return;
 224 
 225         for (i = 0; i < pardist; i++)
 226                 term_vspace(p);
 227 }
 228 
 229 /* ARGSUSED */
 230 static int
 231 pre_ign(DECL_ARGS)
 232 {
 233 
 234         return(0);
 235 }
 236 
 237 
 238 /* ARGSUSED */
 239 static int
 240 pre_I(DECL_ARGS)
 241 {
 242 
 243         term_fontrepl(p, TERMFONT_UNDER);
 244         return(1);
 245 }
 246 
 247 
 248 /* ARGSUSED */
 249 static int
 250 pre_literal(DECL_ARGS)
 251 {
 252 
 253         term_newln(p);
 254 
 255         if (MAN_nf == n->tok || MAN_EX == n->tok)
 256                 mt->fl |= MANT_LITERAL;
 257         else
 258                 mt->fl &= ~MANT_LITERAL;
 259 
 260         /*
 261          * Unlike .IP and .TP, .HP does not have a HEAD.
 262          * So in case a second call to term_flushln() is needed,
 263          * indentation has to be set up explicitly.
 264          */
 265         if (MAN_HP == n->parent->tok && p->rmargin < p->maxrmargin) {
 266                 p->offset = p->rmargin;
 267                 p->rmargin = p->maxrmargin;
 268                 p->trailspace = 0;
 269                 p->flags &= ~TERMP_NOBREAK;
 270                 p->flags |= TERMP_NOSPACE;
 271         }
 272 
 273         return(0);
 274 }
 275 
 276 /* ARGSUSED */
 277 static int
 278 pre_PD(DECL_ARGS)
 279 {
 280 
 281         n = n->child;
 282         if (0 == n) {
 283                 mt->pardist = 1;
 284                 return(0);
 285         }
 286         assert(MAN_TEXT == n->type);
 287         mt->pardist = atoi(n->string);
 288         return(0);
 289 }
 290 
 291 /* ARGSUSED */
 292 static int
 293 pre_alternate(DECL_ARGS)
 294 {
 295         enum termfont            font[2];
 296         const struct man_node   *nn;
 297         int                      savelit, i;
 298 
 299         switch (n->tok) {
 300         case (MAN_RB):
 301                 font[0] = TERMFONT_NONE;
 302                 font[1] = TERMFONT_BOLD;
 303                 break;
 304         case (MAN_RI):
 305                 font[0] = TERMFONT_NONE;
 306                 font[1] = TERMFONT_UNDER;
 307                 break;
 308         case (MAN_BR):
 309                 font[0] = TERMFONT_BOLD;
 310                 font[1] = TERMFONT_NONE;
 311                 break;
 312         case (MAN_BI):
 313                 font[0] = TERMFONT_BOLD;
 314                 font[1] = TERMFONT_UNDER;
 315                 break;
 316         case (MAN_IR):
 317                 font[0] = TERMFONT_UNDER;
 318                 font[1] = TERMFONT_NONE;
 319                 break;
 320         case (MAN_IB):
 321                 font[0] = TERMFONT_UNDER;
 322                 font[1] = TERMFONT_BOLD;
 323                 break;
 324         default:
 325                 abort();
 326         }
 327 
 328         savelit = MANT_LITERAL & mt->fl;
 329         mt->fl &= ~MANT_LITERAL;
 330 
 331         for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) {
 332                 term_fontrepl(p, font[i]);
 333                 if (savelit && NULL == nn->next)
 334                         mt->fl |= MANT_LITERAL;
 335                 print_man_node(p, mt, nn, meta);
 336                 if (nn->next)
 337                         p->flags |= TERMP_NOSPACE;
 338         }
 339 
 340         return(0);
 341 }
 342 
 343 /* ARGSUSED */
 344 static int
 345 pre_B(DECL_ARGS)
 346 {
 347 
 348         term_fontrepl(p, TERMFONT_BOLD);
 349         return(1);
 350 }
 351 
 352 /* ARGSUSED */
 353 static int
 354 pre_OP(DECL_ARGS)
 355 {
 356 
 357         term_word(p, "[");
 358         p->flags |= TERMP_NOSPACE;
 359 
 360         if (NULL != (n = n->child)) {
 361                 term_fontrepl(p, TERMFONT_BOLD);
 362                 term_word(p, n->string);
 363         }
 364         if (NULL != n && NULL != n->next) {
 365                 term_fontrepl(p, TERMFONT_UNDER);
 366                 term_word(p, n->next->string);
 367         }
 368 
 369         term_fontrepl(p, TERMFONT_NONE);
 370         p->flags |= TERMP_NOSPACE;
 371         term_word(p, "]");
 372         return(0);
 373 }
 374 
 375 /* ARGSUSED */
 376 static int
 377 pre_ft(DECL_ARGS)
 378 {
 379         const char      *cp;
 380 
 381         if (NULL == n->child) {
 382                 term_fontlast(p);
 383                 return(0);
 384         }
 385 
 386         cp = n->child->string;
 387         switch (*cp) {
 388         case ('4'):
 389                 /* FALLTHROUGH */
 390         case ('3'):
 391                 /* FALLTHROUGH */
 392         case ('B'):
 393                 term_fontrepl(p, TERMFONT_BOLD);
 394                 break;
 395         case ('2'):
 396                 /* FALLTHROUGH */
 397         case ('I'):
 398                 term_fontrepl(p, TERMFONT_UNDER);
 399                 break;
 400         case ('P'):
 401                 term_fontlast(p);
 402                 break;
 403         case ('1'):
 404                 /* FALLTHROUGH */
 405         case ('C'):
 406                 /* FALLTHROUGH */
 407         case ('R'):
 408                 term_fontrepl(p, TERMFONT_NONE);
 409                 break;
 410         default:
 411                 break;
 412         }
 413         return(0);
 414 }
 415 
 416 /* ARGSUSED */
 417 static int
 418 pre_in(DECL_ARGS)
 419 {
 420         int              len, less;
 421         size_t           v;
 422         const char      *cp;
 423 
 424         term_newln(p);
 425 
 426         if (NULL == n->child) {
 427                 p->offset = mt->offset;
 428                 return(0);
 429         }
 430 
 431         cp = n->child->string;
 432         less = 0;
 433 
 434         if ('-' == *cp)
 435                 less = -1;
 436         else if ('+' == *cp)
 437                 less = 1;
 438         else
 439                 cp--;
 440 
 441         if ((len = a2width(p, ++cp)) < 0)
 442                 return(0);
 443 
 444         v = (size_t)len;
 445 
 446         if (less < 0)
 447                 p->offset -= p->offset > v ? v : p->offset;
 448         else if (less > 0)
 449                 p->offset += v;
 450         else 
 451                 p->offset = v;
 452 
 453         /* Don't let this creep beyond the right margin. */
 454 
 455         if (p->offset > p->rmargin)
 456                 p->offset = p->rmargin;
 457 
 458         return(0);
 459 }
 460 
 461 
 462 /* ARGSUSED */
 463 static int
 464 pre_sp(DECL_ARGS)
 465 {
 466         char            *s;
 467         size_t           i, len;
 468         int              neg;
 469 
 470         if ((NULL == n->prev && n->parent)) {
 471                 switch (n->parent->tok) {
 472                 case (MAN_SH):
 473                         /* FALLTHROUGH */
 474                 case (MAN_SS):
 475                         /* FALLTHROUGH */
 476                 case (MAN_PP):
 477                         /* FALLTHROUGH */
 478                 case (MAN_LP):
 479                         /* FALLTHROUGH */
 480                 case (MAN_P):
 481                         /* FALLTHROUGH */
 482                         return(0);
 483                 default:
 484                         break;
 485                 }
 486         }
 487 
 488         neg = 0;
 489         switch (n->tok) {
 490         case (MAN_br):
 491                 len = 0;
 492                 break;
 493         default:
 494                 if (NULL == n->child) {
 495                         len = 1;
 496                         break;
 497                 }
 498                 s = n->child->string;
 499                 if ('-' == *s) {
 500                         neg = 1;
 501                         s++;
 502                 }
 503                 len = a2height(p, s);
 504                 break;
 505         }
 506 
 507         if (0 == len)
 508                 term_newln(p);
 509         else if (neg)
 510                 p->skipvsp += len;
 511         else
 512                 for (i = 0; i < len; i++)
 513                         term_vspace(p);
 514 
 515         return(0);
 516 }
 517 
 518 
 519 /* ARGSUSED */
 520 static int
 521 pre_HP(DECL_ARGS)
 522 {
 523         size_t                   len, one;
 524         int                      ival;
 525         const struct man_node   *nn;
 526 
 527         switch (n->type) {
 528         case (MAN_BLOCK):
 529                 print_bvspace(p, n, mt->pardist);
 530                 return(1);
 531         case (MAN_BODY):
 532                 break;
 533         default:
 534                 return(0);
 535         }
 536 
 537         if ( ! (MANT_LITERAL & mt->fl)) {
 538                 p->flags |= TERMP_NOBREAK;
 539                 p->trailspace = 2;
 540         }
 541 
 542         len = mt->lmargin[mt->lmargincur];
 543         ival = -1;
 544 
 545         /* Calculate offset. */
 546 
 547         if (NULL != (nn = n->parent->head->child))
 548                 if ((ival = a2width(p, nn->string)) >= 0)
 549                         len = (size_t)ival;
 550 
 551         one = term_len(p, 1);
 552         if (len < one)
 553                 len = one;
 554 
 555         p->offset = mt->offset;
 556         p->rmargin = mt->offset + len;
 557 
 558         if (ival >= 0)
 559                 mt->lmargin[mt->lmargincur] = (size_t)ival;
 560 
 561         return(1);
 562 }
 563 
 564 
 565 /* ARGSUSED */
 566 static void
 567 post_HP(DECL_ARGS)
 568 {
 569 
 570         switch (n->type) {
 571         case (MAN_BODY):
 572                 term_newln(p);
 573                 p->flags &= ~TERMP_NOBREAK;
 574                 p->trailspace = 0;
 575                 p->offset = mt->offset;
 576                 p->rmargin = p->maxrmargin;
 577                 break;
 578         default:
 579                 break;
 580         }
 581 }
 582 
 583 
 584 /* ARGSUSED */
 585 static int
 586 pre_PP(DECL_ARGS)
 587 {
 588 
 589         switch (n->type) {
 590         case (MAN_BLOCK):
 591                 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
 592                 print_bvspace(p, n, mt->pardist);
 593                 break;
 594         default:
 595                 p->offset = mt->offset;
 596                 break;
 597         }
 598 
 599         return(MAN_HEAD != n->type);
 600 }
 601 
 602 
 603 /* ARGSUSED */
 604 static int
 605 pre_IP(DECL_ARGS)
 606 {
 607         const struct man_node   *nn;
 608         size_t                   len;
 609         int                      savelit, ival;
 610 
 611         switch (n->type) {
 612         case (MAN_BODY):
 613                 p->flags |= TERMP_NOSPACE;
 614                 break;
 615         case (MAN_HEAD):
 616                 p->flags |= TERMP_NOBREAK;
 617                 p->trailspace = 1;
 618                 break;
 619         case (MAN_BLOCK):
 620                 print_bvspace(p, n, mt->pardist);
 621                 /* FALLTHROUGH */
 622         default:
 623                 return(1);
 624         }
 625 
 626         len = mt->lmargin[mt->lmargincur];
 627         ival = -1;
 628 
 629         /* Calculate the offset from the optional second argument. */
 630         if (NULL != (nn = n->parent->head->child))
 631                 if (NULL != (nn = nn->next))
 632                         if ((ival = a2width(p, nn->string)) >= 0)
 633                                 len = (size_t)ival;
 634 
 635         switch (n->type) {
 636         case (MAN_HEAD):
 637                 /* Handle zero-width lengths. */
 638                 if (0 == len)
 639                         len = term_len(p, 1);
 640 
 641                 p->offset = mt->offset;
 642                 p->rmargin = mt->offset + len;
 643                 if (ival < 0)
 644                         break;
 645 
 646                 /* Set the saved left-margin. */
 647                 mt->lmargin[mt->lmargincur] = (size_t)ival;
 648 
 649                 savelit = MANT_LITERAL & mt->fl;
 650                 mt->fl &= ~MANT_LITERAL;
 651 
 652                 if (n->child)
 653                         print_man_node(p, mt, n->child, meta);
 654 
 655                 if (savelit)
 656                         mt->fl |= MANT_LITERAL;
 657 
 658                 return(0);
 659         case (MAN_BODY):
 660                 p->offset = mt->offset + len;
 661                 p->rmargin = p->maxrmargin;
 662                 break;
 663         default:
 664                 break;
 665         }
 666 
 667         return(1);
 668 }
 669 
 670 
 671 /* ARGSUSED */
 672 static void
 673 post_IP(DECL_ARGS)
 674 {
 675 
 676         switch (n->type) {
 677         case (MAN_HEAD):
 678                 term_flushln(p);
 679                 p->flags &= ~TERMP_NOBREAK;
 680                 p->trailspace = 0;
 681                 p->rmargin = p->maxrmargin;
 682                 break;
 683         case (MAN_BODY):
 684                 term_newln(p);
 685                 p->offset = mt->offset;
 686                 break;
 687         default:
 688                 break;
 689         }
 690 }
 691 
 692 
 693 /* ARGSUSED */
 694 static int
 695 pre_TP(DECL_ARGS)
 696 {
 697         const struct man_node   *nn;
 698         size_t                   len;
 699         int                      savelit, ival;
 700 
 701         switch (n->type) {
 702         case (MAN_HEAD):
 703                 p->flags |= TERMP_NOBREAK;
 704                 p->trailspace = 1;
 705                 break;
 706         case (MAN_BODY):
 707                 p->flags |= TERMP_NOSPACE;
 708                 break;
 709         case (MAN_BLOCK):
 710                 print_bvspace(p, n, mt->pardist);
 711                 /* FALLTHROUGH */
 712         default:
 713                 return(1);
 714         }
 715 
 716         len = (size_t)mt->lmargin[mt->lmargincur];
 717         ival = -1;
 718 
 719         /* Calculate offset. */
 720 
 721         if (NULL != (nn = n->parent->head->child))
 722                 if (nn->string && nn->parent->line == nn->line)
 723                         if ((ival = a2width(p, nn->string)) >= 0)
 724                                 len = (size_t)ival;
 725 
 726         switch (n->type) {
 727         case (MAN_HEAD):
 728                 /* Handle zero-length properly. */
 729                 if (0 == len)
 730                         len = term_len(p, 1);
 731 
 732                 p->offset = mt->offset;
 733                 p->rmargin = mt->offset + len;
 734 
 735                 savelit = MANT_LITERAL & mt->fl;
 736                 mt->fl &= ~MANT_LITERAL;
 737 
 738                 /* Don't print same-line elements. */
 739                 for (nn = n->child; nn; nn = nn->next)
 740                         if (nn->line > n->line)
 741                                 print_man_node(p, mt, nn, meta);
 742 
 743                 if (savelit)
 744                         mt->fl |= MANT_LITERAL;
 745                 if (ival >= 0)
 746                         mt->lmargin[mt->lmargincur] = (size_t)ival;
 747 
 748                 return(0);
 749         case (MAN_BODY):
 750                 p->offset = mt->offset + len;
 751                 p->rmargin = p->maxrmargin;
 752                 p->trailspace = 0;
 753                 p->flags &= ~TERMP_NOBREAK;
 754                 break;
 755         default:
 756                 break;
 757         }
 758 
 759         return(1);
 760 }
 761 
 762 
 763 /* ARGSUSED */
 764 static void
 765 post_TP(DECL_ARGS)
 766 {
 767 
 768         switch (n->type) {
 769         case (MAN_HEAD):
 770                 term_flushln(p);
 771                 break;
 772         case (MAN_BODY):
 773                 term_newln(p);
 774                 p->offset = mt->offset;
 775                 break;
 776         default:
 777                 break;
 778         }
 779 }
 780 
 781 
 782 /* ARGSUSED */
 783 static int
 784 pre_SS(DECL_ARGS)
 785 {
 786         int      i;
 787 
 788         switch (n->type) {
 789         case (MAN_BLOCK):
 790                 mt->fl &= ~MANT_LITERAL;
 791                 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
 792                 mt->offset = term_len(p, p->defindent);
 793                 /* If following a prior empty `SS', no vspace. */
 794                 if (n->prev && MAN_SS == n->prev->tok)
 795                         if (NULL == n->prev->body->child)
 796                                 break;
 797                 if (NULL == n->prev)
 798                         break;
 799                 for (i = 0; i < mt->pardist; i++)
 800                         term_vspace(p);
 801                 break;
 802         case (MAN_HEAD):
 803                 term_fontrepl(p, TERMFONT_BOLD);
 804                 p->offset = term_len(p, 3);
 805                 break;
 806         case (MAN_BODY):
 807                 p->offset = mt->offset;
 808                 break;
 809         default:
 810                 break;
 811         }
 812 
 813         return(1);
 814 }
 815 
 816 
 817 /* ARGSUSED */
 818 static void
 819 post_SS(DECL_ARGS)
 820 {
 821         
 822         switch (n->type) {
 823         case (MAN_HEAD):
 824                 term_newln(p);
 825                 break;
 826         case (MAN_BODY):
 827                 term_newln(p);
 828                 break;
 829         default:
 830                 break;
 831         }
 832 }
 833 
 834 
 835 /* ARGSUSED */
 836 static int
 837 pre_SH(DECL_ARGS)
 838 {
 839         int      i;
 840 
 841         switch (n->type) {
 842         case (MAN_BLOCK):
 843                 mt->fl &= ~MANT_LITERAL;
 844                 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
 845                 mt->offset = term_len(p, p->defindent);
 846                 /* If following a prior empty `SH', no vspace. */
 847                 if (n->prev && MAN_SH == n->prev->tok)
 848                         if (NULL == n->prev->body->child)
 849                                 break;
 850                 /* If the first macro, no vspae. */
 851                 if (NULL == n->prev)
 852                         break;
 853                 for (i = 0; i < mt->pardist; i++)
 854                         term_vspace(p);
 855                 break;
 856         case (MAN_HEAD):
 857                 term_fontrepl(p, TERMFONT_BOLD);
 858                 p->offset = 0;
 859                 break;
 860         case (MAN_BODY):
 861                 p->offset = mt->offset;
 862                 break;
 863         default:
 864                 break;
 865         }
 866 
 867         return(1);
 868 }
 869 
 870 
 871 /* ARGSUSED */
 872 static void
 873 post_SH(DECL_ARGS)
 874 {
 875         
 876         switch (n->type) {
 877         case (MAN_HEAD):
 878                 term_newln(p);
 879                 break;
 880         case (MAN_BODY):
 881                 term_newln(p);
 882                 break;
 883         default:
 884                 break;
 885         }
 886 }
 887 
 888 /* ARGSUSED */
 889 static int
 890 pre_RS(DECL_ARGS)
 891 {
 892         int              ival;
 893         size_t           sz;
 894 
 895         switch (n->type) {
 896         case (MAN_BLOCK):
 897                 term_newln(p);
 898                 return(1);
 899         case (MAN_HEAD):
 900                 return(0);
 901         default:
 902                 break;
 903         }
 904 
 905         sz = term_len(p, p->defindent);
 906 
 907         if (NULL != (n = n->parent->head->child))
 908                 if ((ival = a2width(p, n->string)) >= 0) 
 909                         sz = (size_t)ival;
 910 
 911         mt->offset += sz;
 912         p->rmargin = p->maxrmargin;
 913         p->offset = mt->offset < p->rmargin ? mt->offset : p->rmargin;
 914 
 915         if (++mt->lmarginsz < MAXMARGINS)
 916                 mt->lmargincur = mt->lmarginsz;
 917 
 918         mt->lmargin[mt->lmargincur] = mt->lmargin[mt->lmargincur - 1];
 919         return(1);
 920 }
 921 
 922 /* ARGSUSED */
 923 static void
 924 post_RS(DECL_ARGS)
 925 {
 926         int              ival;
 927         size_t           sz;
 928 
 929         switch (n->type) {
 930         case (MAN_BLOCK):
 931                 return;
 932         case (MAN_HEAD):
 933                 return;
 934         default:
 935                 term_newln(p);
 936                 break;
 937         }
 938 
 939         sz = term_len(p, p->defindent);
 940 
 941         if (NULL != (n = n->parent->head->child)) 
 942                 if ((ival = a2width(p, n->string)) >= 0) 
 943                         sz = (size_t)ival;
 944 
 945         mt->offset = mt->offset < sz ?  0 : mt->offset - sz;
 946         p->offset = mt->offset;
 947 
 948         if (--mt->lmarginsz < MAXMARGINS)
 949                 mt->lmargincur = mt->lmarginsz;
 950 }
 951 
 952 /* ARGSUSED */
 953 static int
 954 pre_UR(DECL_ARGS)
 955 {
 956 
 957         return (MAN_HEAD != n->type);
 958 }
 959 
 960 /* ARGSUSED */
 961 static void
 962 post_UR(DECL_ARGS)
 963 {
 964 
 965         if (MAN_BLOCK != n->type)
 966                 return;
 967 
 968         term_word(p, "<");
 969         p->flags |= TERMP_NOSPACE;
 970 
 971         if (NULL != n->child->child)
 972                 print_man_node(p, mt, n->child->child, meta);
 973 
 974         p->flags |= TERMP_NOSPACE;
 975         term_word(p, ">");
 976 }
 977 
 978 static void
 979 print_man_node(DECL_ARGS)
 980 {
 981         size_t           rm, rmax;
 982         int              c;
 983 
 984         switch (n->type) {
 985         case(MAN_TEXT):
 986                 /*
 987                  * If we have a blank line, output a vertical space.
 988                  * If we have a space as the first character, break
 989                  * before printing the line's data.
 990                  */
 991                 if ('\0' == *n->string) {
 992                         term_vspace(p);
 993                         return;
 994                 } else if (' ' == *n->string && MAN_LINE & n->flags)
 995                         term_newln(p);
 996 
 997                 term_word(p, n->string);
 998                 goto out;
 999 
1000         case (MAN_EQN):
1001                 term_eqn(p, n->eqn);
1002                 return;
1003         case (MAN_TBL):
1004                 /*
1005                  * Tables are preceded by a newline.  Then process a
1006                  * table line, which will cause line termination,
1007                  */
1008                 if (TBL_SPAN_FIRST & n->span->flags) 
1009                         term_newln(p);
1010                 term_tbl(p, n->span);
1011                 return;
1012         default:
1013                 break;
1014         }
1015 
1016         if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
1017                 term_fontrepl(p, TERMFONT_NONE);
1018 
1019         c = 1;
1020         if (termacts[n->tok].pre)
1021                 c = (*termacts[n->tok].pre)(p, mt, n, meta);
1022 
1023         if (c && n->child)
1024                 print_man_nodelist(p, mt, n->child, meta);
1025 
1026         if (termacts[n->tok].post)
1027                 (*termacts[n->tok].post)(p, mt, n, meta);
1028         if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
1029                 term_fontrepl(p, TERMFONT_NONE);
1030 
1031 out:
1032         /*
1033          * If we're in a literal context, make sure that words
1034          * together on the same line stay together.  This is a
1035          * POST-printing call, so we check the NEXT word.  Since
1036          * -man doesn't have nested macros, we don't need to be
1037          * more specific than this.
1038          */
1039         if (MANT_LITERAL & mt->fl && ! (TERMP_NOBREAK & p->flags) &&
1040             (NULL == n->next || n->next->line > n->line)) {
1041                 rm = p->rmargin;
1042                 rmax = p->maxrmargin;
1043                 p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
1044                 p->flags |= TERMP_NOSPACE;
1045                 if (NULL != n->string && '\0' != *n->string)
1046                         term_flushln(p);
1047                 else
1048                         term_newln(p);
1049                 if (rm < rmax && n->parent->tok == MAN_HP) {
1050                         p->offset = rm;
1051                         p->rmargin = rmax;
1052                 } else
1053                         p->rmargin = rm;
1054                 p->maxrmargin = rmax;
1055         }
1056         if (MAN_EOS & n->flags)
1057                 p->flags |= TERMP_SENTENCE;
1058 }
1059 
1060 
1061 static void
1062 print_man_nodelist(DECL_ARGS)
1063 {
1064 
1065         print_man_node(p, mt, n, meta);
1066         if ( ! n->next)
1067                 return;
1068         print_man_nodelist(p, mt, n->next, meta);
1069 }
1070 
1071 
1072 static void
1073 print_man_foot(struct termp *p, const void *arg)
1074 {
1075         char            title[BUFSIZ];
1076         size_t          datelen;
1077         const struct man_meta *meta;
1078 
1079         meta = (const struct man_meta *)arg;
1080         assert(meta->title);
1081         assert(meta->msec);
1082         assert(meta->date);
1083 
1084         term_fontrepl(p, TERMFONT_NONE);
1085 
1086         term_vspace(p);
1087 
1088         /*
1089          * Temporary, undocumented option to imitate mdoc(7) output.
1090          * In the bottom right corner, use the source instead of
1091          * the title.
1092          */
1093 
1094         if ( ! p->mdocstyle) {
1095                 term_vspace(p);
1096                 term_vspace(p);
1097                 snprintf(title, BUFSIZ, "%s(%s)", meta->title, meta->msec);
1098         } else if (meta->source) {
1099                 strlcpy(title, meta->source, BUFSIZ);
1100         } else {
1101                 title[0] = '\0';
1102         }
1103         datelen = term_strlen(p, meta->date);
1104 
1105         /* Bottom left corner: manual source. */
1106 
1107         p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
1108         p->trailspace = 1;
1109         p->offset = 0;
1110         p->rmargin = (p->maxrmargin - datelen + term_len(p, 1)) / 2;
1111 
1112         if (meta->source)
1113                 term_word(p, meta->source);
1114         term_flushln(p);
1115 
1116         /* At the bottom in the middle: manual date. */
1117 
1118         p->flags |= TERMP_NOSPACE;
1119         p->offset = p->rmargin;
1120         p->rmargin = p->maxrmargin - term_strlen(p, title);
1121         if (p->offset + datelen >= p->rmargin)
1122                 p->rmargin = p->offset + datelen;
1123 
1124         term_word(p, meta->date);
1125         term_flushln(p);
1126 
1127         /* Bottom right corner: manual title and section. */
1128 
1129         p->flags &= ~TERMP_NOBREAK;
1130         p->flags |= TERMP_NOSPACE;
1131         p->trailspace = 0;
1132         p->offset = p->rmargin;
1133         p->rmargin = p->maxrmargin;
1134 
1135         term_word(p, title);
1136         term_flushln(p);
1137 }
1138 
1139 
1140 static void
1141 print_man_head(struct termp *p, const void *arg)
1142 {
1143         char            buf[BUFSIZ], title[BUFSIZ];
1144         size_t          buflen, titlen;
1145         const struct man_meta *meta;
1146 
1147         meta = (const struct man_meta *)arg;
1148         assert(meta->title);
1149         assert(meta->msec);
1150 
1151         if (meta->vol)
1152                 strlcpy(buf, meta->vol, BUFSIZ);
1153         else
1154                 buf[0] = '\0';
1155         buflen = term_strlen(p, buf);
1156 
1157         /* Top left corner: manual title and section. */
1158 
1159         snprintf(title, BUFSIZ, "%s(%s)", meta->title, meta->msec);
1160         titlen = term_strlen(p, title);
1161 
1162         p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1163         p->trailspace = 1;
1164         p->offset = 0;
1165         p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ?
1166             (p->maxrmargin - 
1167              term_strlen(p, buf) + term_len(p, 1)) / 2 :
1168             p->maxrmargin - buflen;
1169 
1170         term_word(p, title);
1171         term_flushln(p);
1172 
1173         /* At the top in the middle: manual volume. */
1174 
1175         p->flags |= TERMP_NOSPACE;
1176         p->offset = p->rmargin;
1177         p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
1178             p->maxrmargin - titlen : p->maxrmargin;
1179 
1180         term_word(p, buf);
1181         term_flushln(p);
1182 
1183         /* Top right corner: title and section, again. */
1184 
1185         p->flags &= ~TERMP_NOBREAK;
1186         p->trailspace = 0;
1187         if (p->rmargin + titlen <= p->maxrmargin) {
1188                 p->flags |= TERMP_NOSPACE;
1189                 p->offset = p->rmargin;
1190                 p->rmargin = p->maxrmargin;
1191                 term_word(p, title);
1192                 term_flushln(p);
1193         }
1194 
1195         p->flags &= ~TERMP_NOSPACE;
1196         p->offset = 0;
1197         p->rmargin = p->maxrmargin;
1198 
1199         /* 
1200          * Groff prints three blank lines before the content.
1201          * Do the same, except in the temporary, undocumented
1202          * mode imitating mdoc(7) output.
1203          */
1204 
1205         term_vspace(p);
1206         if ( ! p->mdocstyle) {
1207                 term_vspace(p);
1208                 term_vspace(p);
1209         }
1210 }