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