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