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