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