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