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