1 /*      $Id: man_term.c,v 1.209 2017/07/31 15:19:06 schwarze Exp $ */
   2 /*
   3  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
   4  * Copyright (c) 2010-2015, 2017 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->tok == MAN_SS && n->body->child == NULL))
 679                         break;
 680 
 681                 for (i = 0; i < mt->pardist; i++)
 682                         term_vspace(p);
 683                 break;
 684         case ROFFT_HEAD:
 685                 term_fontrepl(p, TERMFONT_BOLD);
 686                 p->tcol->offset = term_len(p, 3);
 687                 p->tcol->rmargin = mt->offset;
 688                 p->trailspace = mt->offset;
 689                 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
 690                 break;
 691         case ROFFT_BODY:
 692                 p->tcol->offset = mt->offset;
 693                 p->tcol->rmargin = p->maxrmargin;
 694                 p->trailspace = 0;
 695                 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
 696                 break;
 697         default:
 698                 break;
 699         }
 700 
 701         return 1;
 702 }
 703 
 704 static void
 705 post_SS(DECL_ARGS)
 706 {
 707 
 708         switch (n->type) {
 709         case ROFFT_HEAD:
 710                 term_newln(p);
 711                 break;
 712         case ROFFT_BODY:
 713                 term_newln(p);
 714                 break;
 715         default:
 716                 break;
 717         }
 718 }
 719 
 720 static int
 721 pre_SH(DECL_ARGS)
 722 {
 723         int      i;
 724 
 725         switch (n->type) {
 726         case ROFFT_BLOCK:
 727                 mt->fl &= ~MANT_LITERAL;
 728                 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
 729                 mt->offset = term_len(p, p->defindent);
 730 
 731                 /*
 732                  * No vertical space before the first section
 733                  * and after an empty section.
 734                  */
 735 
 736                 do {
 737                         n = n->prev;
 738                 } while (n != NULL && n->tok >= MAN_TH &&
 739                     termacts[n->tok].flags & MAN_NOTEXT);
 740                 if (n == NULL || (n->tok == MAN_SH && n->body->child == NULL))
 741                         break;
 742 
 743                 for (i = 0; i < mt->pardist; i++)
 744                         term_vspace(p);
 745                 break;
 746         case ROFFT_HEAD:
 747                 term_fontrepl(p, TERMFONT_BOLD);
 748                 p->tcol->offset = 0;
 749                 p->tcol->rmargin = mt->offset;
 750                 p->trailspace = mt->offset;
 751                 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
 752                 break;
 753         case ROFFT_BODY:
 754                 p->tcol->offset = mt->offset;
 755                 p->tcol->rmargin = p->maxrmargin;
 756                 p->trailspace = 0;
 757                 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
 758                 break;
 759         default:
 760                 break;
 761         }
 762 
 763         return 1;
 764 }
 765 
 766 static void
 767 post_SH(DECL_ARGS)
 768 {
 769 
 770         switch (n->type) {
 771         case ROFFT_HEAD:
 772                 term_newln(p);
 773                 break;
 774         case ROFFT_BODY:
 775                 term_newln(p);
 776                 break;
 777         default:
 778                 break;
 779         }
 780 }
 781 
 782 static int
 783 pre_RS(DECL_ARGS)
 784 {
 785         struct roffsu    su;
 786 
 787         switch (n->type) {
 788         case ROFFT_BLOCK:
 789                 term_newln(p);
 790                 return 1;
 791         case ROFFT_HEAD:
 792                 return 0;
 793         default:
 794                 break;
 795         }
 796 
 797         n = n->parent->head;
 798         n->aux = SHRT_MAX + 1;
 799         if (n->child == NULL)
 800                 n->aux = mt->lmargin[mt->lmargincur];
 801         else if (a2roffsu(n->child->string, &su, SCALE_EN) != NULL)
 802                 n->aux = term_hen(p, &su);
 803         if (n->aux < 0 && (size_t)(-n->aux) > mt->offset)
 804                 n->aux = -mt->offset;
 805         else if (n->aux > SHRT_MAX)
 806                 n->aux = term_len(p, p->defindent);
 807 
 808         mt->offset += n->aux;
 809         p->tcol->offset = mt->offset;
 810         p->tcol->rmargin = p->maxrmargin;
 811 
 812         if (++mt->lmarginsz < MAXMARGINS)
 813                 mt->lmargincur = mt->lmarginsz;
 814 
 815         mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
 816         return 1;
 817 }
 818 
 819 static void
 820 post_RS(DECL_ARGS)
 821 {
 822 
 823         switch (n->type) {
 824         case ROFFT_BLOCK:
 825                 return;
 826         case ROFFT_HEAD:
 827                 return;
 828         default:
 829                 term_newln(p);
 830                 break;
 831         }
 832 
 833         mt->offset -= n->parent->head->aux;
 834         p->tcol->offset = mt->offset;
 835 
 836         if (--mt->lmarginsz < MAXMARGINS)
 837                 mt->lmargincur = mt->lmarginsz;
 838 }
 839 
 840 static int
 841 pre_UR(DECL_ARGS)
 842 {
 843 
 844         return n->type != ROFFT_HEAD;
 845 }
 846 
 847 static void
 848 post_UR(DECL_ARGS)
 849 {
 850 
 851         if (n->type != ROFFT_BLOCK)
 852                 return;
 853 
 854         term_word(p, "<");
 855         p->flags |= TERMP_NOSPACE;
 856 
 857         if (NULL != n->child->child)
 858                 print_man_node(p, mt, n->child->child, meta);
 859 
 860         p->flags |= TERMP_NOSPACE;
 861         term_word(p, ">");
 862 }
 863 
 864 static void
 865 print_man_node(DECL_ARGS)
 866 {
 867         int              c;
 868 
 869         switch (n->type) {
 870         case ROFFT_TEXT:
 871                 /*
 872                  * If we have a blank line, output a vertical space.
 873                  * If we have a space as the first character, break
 874                  * before printing the line's data.
 875                  */
 876                 if (*n->string == '\0') {
 877                         if (p->flags & TERMP_NONEWLINE)
 878                                 term_newln(p);
 879                         else
 880                                 term_vspace(p);
 881                         return;
 882                 } else if (*n->string == ' ' && n->flags & NODE_LINE &&
 883                     (p->flags & TERMP_NONEWLINE) == 0)
 884                         term_newln(p);
 885 
 886                 term_word(p, n->string);
 887                 goto out;
 888 
 889         case ROFFT_EQN:
 890                 if ( ! (n->flags & NODE_LINE))
 891                         p->flags |= TERMP_NOSPACE;
 892                 term_eqn(p, n->eqn);
 893                 if (n->next != NULL && ! (n->next->flags & NODE_LINE))
 894                         p->flags |= TERMP_NOSPACE;
 895                 return;
 896         case ROFFT_TBL:
 897                 if (p->tbl.cols == NULL)
 898                         term_vspace(p);
 899                 term_tbl(p, n->span);
 900                 return;
 901         default:
 902                 break;
 903         }
 904 
 905         if (n->tok < ROFF_MAX) {
 906                 roff_term_pre(p, n);
 907                 return;
 908         }
 909 
 910         assert(n->tok >= MAN_TH && n->tok <= MAN_MAX);
 911         if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
 912                 term_fontrepl(p, TERMFONT_NONE);
 913 
 914         c = 1;
 915         if (termacts[n->tok].pre)
 916                 c = (*termacts[n->tok].pre)(p, mt, n, meta);
 917 
 918         if (c && n->child)
 919                 print_man_nodelist(p, mt, n->child, meta);
 920 
 921         if (termacts[n->tok].post)
 922                 (*termacts[n->tok].post)(p, mt, n, meta);
 923         if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
 924                 term_fontrepl(p, TERMFONT_NONE);
 925 
 926 out:
 927         /*
 928          * If we're in a literal context, make sure that words
 929          * together on the same line stay together.  This is a
 930          * POST-printing call, so we check the NEXT word.  Since
 931          * -man doesn't have nested macros, we don't need to be
 932          * more specific than this.
 933          */
 934         if (mt->fl & MANT_LITERAL &&
 935             ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) &&
 936             (n->next == NULL || n->next->flags & NODE_LINE)) {
 937                 p->flags |= TERMP_BRNEVER | TERMP_NOSPACE;
 938                 if (n->string != NULL && *n->string != '\0')
 939                         term_flushln(p);
 940                 else
 941                         term_newln(p);
 942                 p->flags &= ~TERMP_BRNEVER;
 943                 if (p->tcol->rmargin < p->maxrmargin &&
 944                     n->parent->tok == MAN_HP) {
 945                         p->tcol->offset = p->tcol->rmargin;
 946                         p->tcol->rmargin = p->maxrmargin;
 947                 }
 948         }
 949         if (NODE_EOS & n->flags)
 950                 p->flags |= TERMP_SENTENCE;
 951 }
 952 
 953 
 954 static void
 955 print_man_nodelist(DECL_ARGS)
 956 {
 957 
 958         while (n != NULL) {
 959                 print_man_node(p, mt, n, meta);
 960                 n = n->next;
 961         }
 962 }
 963 
 964 static void
 965 print_man_foot(struct termp *p, const struct roff_meta *meta)
 966 {
 967         char                    *title;
 968         size_t                   datelen, titlen;
 969 
 970         assert(meta->title);
 971         assert(meta->msec);
 972         assert(meta->date);
 973 
 974         term_fontrepl(p, TERMFONT_NONE);
 975 
 976         if (meta->hasbody)
 977                 term_vspace(p);
 978 
 979         /*
 980          * Temporary, undocumented option to imitate mdoc(7) output.
 981          * In the bottom right corner, use the operating system
 982          * instead of the title.
 983          */
 984 
 985         if ( ! p->mdocstyle) {
 986                 if (meta->hasbody) {
 987                         term_vspace(p);
 988                         term_vspace(p);
 989                 }
 990                 mandoc_asprintf(&title, "%s(%s)",
 991                     meta->title, meta->msec);
 992         } else if (meta->os) {
 993                 title = mandoc_strdup(meta->os);
 994         } else {
 995                 title = mandoc_strdup("");
 996         }
 997         datelen = term_strlen(p, meta->date);
 998 
 999         /* Bottom left corner: operating system. */
1000 
1001         p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
1002         p->trailspace = 1;
1003         p->tcol->offset = 0;
1004         p->tcol->rmargin = p->maxrmargin > datelen ?
1005             (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0;
1006 
1007         if (meta->os)
1008                 term_word(p, meta->os);
1009         term_flushln(p);
1010 
1011         /* At the bottom in the middle: manual date. */
1012 
1013         p->tcol->offset = p->tcol->rmargin;
1014         titlen = term_strlen(p, title);
1015         p->tcol->rmargin = p->maxrmargin > titlen ?
1016             p->maxrmargin - titlen : 0;
1017         p->flags |= TERMP_NOSPACE;
1018 
1019         term_word(p, meta->date);
1020         term_flushln(p);
1021 
1022         /* Bottom right corner: manual title and section. */
1023 
1024         p->flags &= ~TERMP_NOBREAK;
1025         p->flags |= TERMP_NOSPACE;
1026         p->trailspace = 0;
1027         p->tcol->offset = p->tcol->rmargin;
1028         p->tcol->rmargin = p->maxrmargin;
1029 
1030         term_word(p, title);
1031         term_flushln(p);
1032         free(title);
1033 }
1034 
1035 static void
1036 print_man_head(struct termp *p, const struct roff_meta *meta)
1037 {
1038         const char              *volume;
1039         char                    *title;
1040         size_t                   vollen, titlen;
1041 
1042         assert(meta->title);
1043         assert(meta->msec);
1044 
1045         volume = NULL == meta->vol ? "" : meta->vol;
1046         vollen = term_strlen(p, volume);
1047 
1048         /* Top left corner: manual title and section. */
1049 
1050         mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec);
1051         titlen = term_strlen(p, title);
1052 
1053         p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1054         p->trailspace = 1;
1055         p->tcol->offset = 0;
1056         p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
1057             (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
1058             vollen < p->maxrmargin ? p->maxrmargin - vollen : 0;
1059 
1060         term_word(p, title);
1061         term_flushln(p);
1062 
1063         /* At the top in the middle: manual volume. */
1064 
1065         p->flags |= TERMP_NOSPACE;
1066         p->tcol->offset = p->tcol->rmargin;
1067         p->tcol->rmargin = p->tcol->offset + vollen + titlen <
1068             p->maxrmargin ?  p->maxrmargin - titlen : p->maxrmargin;
1069 
1070         term_word(p, volume);
1071         term_flushln(p);
1072 
1073         /* Top right corner: title and section, again. */
1074 
1075         p->flags &= ~TERMP_NOBREAK;
1076         p->trailspace = 0;
1077         if (p->tcol->rmargin + titlen <= p->maxrmargin) {
1078                 p->flags |= TERMP_NOSPACE;
1079                 p->tcol->offset = p->tcol->rmargin;
1080                 p->tcol->rmargin = p->maxrmargin;
1081                 term_word(p, title);
1082                 term_flushln(p);
1083         }
1084 
1085         p->flags &= ~TERMP_NOSPACE;
1086         p->tcol->offset = 0;
1087         p->tcol->rmargin = p->maxrmargin;
1088 
1089         /*
1090          * Groff prints three blank lines before the content.
1091          * Do the same, except in the temporary, undocumented
1092          * mode imitating mdoc(7) output.
1093          */
1094 
1095         term_vspace(p);
1096         if ( ! p->mdocstyle) {
1097                 term_vspace(p);
1098                 term_vspace(p);
1099         }
1100         free(title);
1101 }