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