1 /* $Id: man_term.c,v 1.16 2009/09/21 20:57:57 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 #include <sys/types.h> 18 19 #include <assert.h> 20 #include <ctype.h> 21 #include <err.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 26 #include "term.h" 27 #include "man.h" 28 29 #define INDENT 7 30 #define HALFINDENT 3 31 32 struct mtermp { 33 int fl; 34 #define MANT_LITERAL (1 << 0) 35 /* 36 * Default amount to indent the left margin after leading text 37 * has been printed (e.g., `HP' left-indent, `TP' and `IP' body 38 * indent). This needs to be saved because `HP' and so on, if 39 * not having a specified value, must default. 40 * 41 * Note that this is the indentation AFTER the left offset, so 42 * the total offset is usually offset + lmargin. 43 */ 44 size_t lmargin; 45 /* 46 * The default offset, i.e., the amount between any text and the 47 * page boundary. 48 */ 49 size_t offset; 50 }; 51 52 #define DECL_ARGS struct termp *p, \ 53 struct mtermp *mt, \ 54 const struct man_node *n, \ 55 const struct man_meta *m 56 57 struct termact { 58 int (*pre)(DECL_ARGS); 59 void (*post)(DECL_ARGS); 60 }; 61 62 static int pre_B(DECL_ARGS); 63 static int pre_BI(DECL_ARGS); 64 static int pre_BR(DECL_ARGS); 65 static int pre_HP(DECL_ARGS); 66 static int pre_I(DECL_ARGS); 67 static int pre_IB(DECL_ARGS); 68 static int pre_IP(DECL_ARGS); 69 static int pre_IR(DECL_ARGS); 70 static int pre_PP(DECL_ARGS); 71 static int pre_RB(DECL_ARGS); 72 static int pre_RI(DECL_ARGS); 73 static int pre_RS(DECL_ARGS); 74 static int pre_SH(DECL_ARGS); 75 static int pre_SS(DECL_ARGS); 76 static int pre_TP(DECL_ARGS); 77 static int pre_br(DECL_ARGS); 78 static int pre_fi(DECL_ARGS); 79 static int pre_ign(DECL_ARGS); 80 static int pre_nf(DECL_ARGS); 81 static int pre_r(DECL_ARGS); 82 static int pre_sp(DECL_ARGS); 83 84 static void post_B(DECL_ARGS); 85 static void post_I(DECL_ARGS); 86 static void post_IP(DECL_ARGS); 87 static void post_HP(DECL_ARGS); 88 static void post_RS(DECL_ARGS); 89 static void post_SH(DECL_ARGS); 90 static void post_SS(DECL_ARGS); 91 static void post_TP(DECL_ARGS); 92 static void post_i(DECL_ARGS); 93 94 static const struct termact termacts[MAN_MAX] = { 95 { pre_br, NULL }, /* br */ 96 { NULL, NULL }, /* TH */ 97 { pre_SH, post_SH }, /* SH */ 98 { pre_SS, post_SS }, /* SS */ 99 { pre_TP, post_TP }, /* TP */ 100 { pre_PP, NULL }, /* LP */ 101 { pre_PP, NULL }, /* PP */ 102 { pre_PP, NULL }, /* P */ 103 { pre_IP, post_IP }, /* IP */ 104 { pre_HP, post_HP }, /* HP */ 105 { NULL, NULL }, /* SM */ 106 { pre_B, post_B }, /* SB */ 107 { pre_BI, NULL }, /* BI */ 108 { pre_IB, NULL }, /* IB */ 109 { pre_BR, NULL }, /* BR */ 110 { pre_RB, NULL }, /* RB */ 111 { NULL, NULL }, /* R */ 112 { pre_B, post_B }, /* B */ 113 { pre_I, post_I }, /* I */ 114 { pre_IR, NULL }, /* IR */ 115 { pre_RI, NULL }, /* RI */ 116 { NULL, NULL }, /* na */ 117 { pre_I, post_i }, /* i */ 118 { pre_sp, NULL }, /* sp */ 119 { pre_nf, NULL }, /* nf */ 120 { pre_fi, NULL }, /* fi */ 121 { pre_r, NULL }, /* r */ 122 { NULL, NULL }, /* RE */ 123 { pre_RS, post_RS }, /* RS */ 124 { pre_ign, NULL }, /* DT */ 125 { pre_ign, NULL }, /* UC */ 126 }; 127 128 static void print_head(struct termp *, 129 const struct man_meta *); 130 static void print_body(DECL_ARGS); 131 static void print_node(DECL_ARGS); 132 static void print_foot(struct termp *, 133 const struct man_meta *); 134 static void fmt_block_vspace(struct termp *, 135 const struct man_node *); 136 static int arg_width(const struct man_node *); 137 138 139 void 140 man_run(struct termp *p, const struct man *m) 141 { 142 struct mtermp mt; 143 144 print_head(p, man_meta(m)); 145 p->flags |= TERMP_NOSPACE; 146 147 mt.fl = 0; 148 mt.lmargin = INDENT; 149 mt.offset = INDENT; 150 151 if (man_node(m)->child) 152 print_body(p, &mt, man_node(m)->child, man_meta(m)); 153 print_foot(p, man_meta(m)); 154 } 155 156 157 static void 158 fmt_block_vspace(struct termp *p, const struct man_node *n) 159 { 160 term_newln(p); 161 162 if (NULL == n->prev) 163 return; 164 165 if (MAN_SS == n->prev->tok) 166 return; 167 if (MAN_SH == n->prev->tok) 168 return; 169 170 term_vspace(p); 171 } 172 173 174 static int 175 arg_width(const struct man_node *n) 176 { 177 int i, len; 178 const char *p; 179 180 assert(MAN_TEXT == n->type); 181 assert(n->string); 182 183 p = n->string; 184 185 if (0 == (len = (int)strlen(p))) 186 return(-1); 187 188 for (i = 0; i < len; i++) 189 if ( ! isdigit((u_char)p[i])) 190 break; 191 192 if (i == len - 1) { 193 if ('n' == p[len - 1] || 'm' == p[len - 1]) 194 return(atoi(p)); 195 } else if (i == len) 196 return(atoi(p)); 197 198 return(-1); 199 } 200 201 202 /* ARGSUSED */ 203 static int 204 pre_ign(DECL_ARGS) 205 { 206 207 return(0); 208 } 209 210 211 /* ARGSUSED */ 212 static int 213 pre_I(DECL_ARGS) 214 { 215 216 p->under++; 217 return(1); 218 } 219 220 221 /* ARGSUSED */ 222 static int 223 pre_r(DECL_ARGS) 224 { 225 226 p->bold = p->under = 0; 227 return(1); 228 } 229 230 231 /* ARGSUSED */ 232 static void 233 post_i(DECL_ARGS) 234 { 235 236 if (n->nchild) 237 p->under--; 238 } 239 240 241 /* ARGSUSED */ 242 static void 243 post_I(DECL_ARGS) 244 { 245 246 p->under--; 247 } 248 249 250 /* ARGSUSED */ 251 static int 252 pre_fi(DECL_ARGS) 253 { 254 255 mt->fl &= ~MANT_LITERAL; 256 return(1); 257 } 258 259 260 /* ARGSUSED */ 261 static int 262 pre_nf(DECL_ARGS) 263 { 264 265 term_newln(p); 266 mt->fl |= MANT_LITERAL; 267 return(1); 268 } 269 270 271 /* ARGSUSED */ 272 static int 273 pre_IR(DECL_ARGS) 274 { 275 const struct man_node *nn; 276 int i; 277 278 for (i = 0, nn = n->child; nn; nn = nn->next, i++) { 279 if ( ! (i % 2)) 280 p->under++; 281 if (i > 0) 282 p->flags |= TERMP_NOSPACE; 283 print_node(p, mt, nn, m); 284 if ( ! (i % 2)) 285 p->under--; 286 } 287 return(0); 288 } 289 290 291 /* ARGSUSED */ 292 static int 293 pre_IB(DECL_ARGS) 294 { 295 const struct man_node *nn; 296 int i; 297 298 for (i = 0, nn = n->child; nn; nn = nn->next, i++) { 299 if (i % 2) 300 p->bold++; 301 else 302 p->under++; 303 if (i > 0) 304 p->flags |= TERMP_NOSPACE; 305 print_node(p, mt, nn, m); 306 if (i % 2) 307 p->bold--; 308 else 309 p->under--; 310 } 311 return(0); 312 } 313 314 315 /* ARGSUSED */ 316 static int 317 pre_RB(DECL_ARGS) 318 { 319 const struct man_node *nn; 320 int i; 321 322 for (i = 0, nn = n->child; nn; nn = nn->next, i++) { 323 if (i % 2) 324 p->bold++; 325 if (i > 0) 326 p->flags |= TERMP_NOSPACE; 327 print_node(p, mt, nn, m); 328 if (i % 2) 329 p->bold--; 330 } 331 return(0); 332 } 333 334 335 /* ARGSUSED */ 336 static int 337 pre_RI(DECL_ARGS) 338 { 339 const struct man_node *nn; 340 int i; 341 342 for (i = 0, nn = n->child; nn; nn = nn->next, i++) { 343 if ( ! (i % 2)) 344 p->under++; 345 if (i > 0) 346 p->flags |= TERMP_NOSPACE; 347 print_node(p, mt, nn, m); 348 if ( ! (i % 2)) 349 p->under--; 350 } 351 return(0); 352 } 353 354 355 /* ARGSUSED */ 356 static int 357 pre_BR(DECL_ARGS) 358 { 359 const struct man_node *nn; 360 int i; 361 362 for (i = 0, nn = n->child; nn; nn = nn->next, i++) { 363 if ( ! (i % 2)) 364 p->bold++; 365 if (i > 0) 366 p->flags |= TERMP_NOSPACE; 367 print_node(p, mt, nn, m); 368 if ( ! (i % 2)) 369 p->bold--; 370 } 371 return(0); 372 } 373 374 375 /* ARGSUSED */ 376 static int 377 pre_BI(DECL_ARGS) 378 { 379 const struct man_node *nn; 380 int i; 381 382 for (i = 0, nn = n->child; nn; nn = nn->next, i++) { 383 if (i % 2) 384 p->under++; 385 else 386 p->bold++; 387 if (i > 0) 388 p->flags |= TERMP_NOSPACE; 389 print_node(p, mt, nn, m); 390 if (i % 2) 391 p->under--; 392 else 393 p->bold--; 394 } 395 return(0); 396 } 397 398 399 /* ARGSUSED */ 400 static int 401 pre_B(DECL_ARGS) 402 { 403 404 p->bold++; 405 return(1); 406 } 407 408 409 /* ARGSUSED */ 410 static void 411 post_B(DECL_ARGS) 412 { 413 414 p->bold--; 415 } 416 417 418 /* ARGSUSED */ 419 static int 420 pre_sp(DECL_ARGS) 421 { 422 int i, len; 423 424 if (NULL == n->child) { 425 term_vspace(p); 426 return(0); 427 } 428 429 len = atoi(n->child->string); 430 if (0 == len) 431 term_newln(p); 432 for (i = 0; i < len; i++) 433 term_vspace(p); 434 435 return(0); 436 } 437 438 439 /* ARGSUSED */ 440 static int 441 pre_br(DECL_ARGS) 442 { 443 444 term_newln(p); 445 return(0); 446 } 447 448 449 /* ARGSUSED */ 450 static int 451 pre_HP(DECL_ARGS) 452 { 453 size_t len; 454 int ival; 455 const struct man_node *nn; 456 457 switch (n->type) { 458 case (MAN_BLOCK): 459 fmt_block_vspace(p, n); 460 return(1); 461 case (MAN_BODY): 462 p->flags |= TERMP_NOBREAK; 463 p->flags |= TERMP_TWOSPACE; 464 break; 465 default: 466 return(0); 467 } 468 469 len = mt->lmargin; 470 ival = -1; 471 472 /* Calculate offset. */ 473 474 if (NULL != (nn = n->parent->head->child)) 475 if ((ival = arg_width(nn)) >= 0) 476 len = (size_t)ival; 477 478 if (0 == len) 479 len = 1; 480 481 p->offset = mt->offset; 482 p->rmargin = mt->offset + len; 483 484 if (ival >= 0) 485 mt->lmargin = (size_t)ival; 486 487 return(1); 488 } 489 490 491 /* ARGSUSED */ 492 static void 493 post_HP(DECL_ARGS) 494 { 495 496 switch (n->type) { 497 case (MAN_BLOCK): 498 term_flushln(p); 499 break; 500 case (MAN_BODY): 501 term_flushln(p); 502 p->flags &= ~TERMP_NOBREAK; 503 p->flags &= ~TERMP_TWOSPACE; 504 p->offset = mt->offset; 505 p->rmargin = p->maxrmargin; 506 break; 507 default: 508 break; 509 } 510 } 511 512 513 /* ARGSUSED */ 514 static int 515 pre_PP(DECL_ARGS) 516 { 517 518 switch (n->type) { 519 case (MAN_BLOCK): 520 mt->lmargin = INDENT; 521 fmt_block_vspace(p, n); 522 break; 523 default: 524 p->offset = mt->offset; 525 break; 526 } 527 528 return(1); 529 } 530 531 532 /* ARGSUSED */ 533 static int 534 pre_IP(DECL_ARGS) 535 { 536 const struct man_node *nn; 537 size_t len; 538 int ival; 539 540 switch (n->type) { 541 case (MAN_BODY): 542 p->flags |= TERMP_NOLPAD; 543 p->flags |= TERMP_NOSPACE; 544 break; 545 case (MAN_HEAD): 546 p->flags |= TERMP_NOBREAK; 547 p->flags |= TERMP_TWOSPACE; 548 break; 549 case (MAN_BLOCK): 550 fmt_block_vspace(p, n); 551 /* FALLTHROUGH */ 552 default: 553 return(1); 554 } 555 556 len = mt->lmargin; 557 ival = -1; 558 559 /* Calculate offset. */ 560 561 if (NULL != (nn = n->parent->head->child)) 562 if (NULL != (nn = nn->next)) { 563 for ( ; nn->next; nn = nn->next) 564 /* Do nothing. */ ; 565 if ((ival = arg_width(nn)) >= 0) 566 len = (size_t)ival; 567 } 568 569 switch (n->type) { 570 case (MAN_HEAD): 571 /* Handle zero-width lengths. */ 572 if (0 == len) 573 len = 1; 574 575 p->offset = mt->offset; 576 p->rmargin = mt->offset + len; 577 if (ival < 0) 578 break; 579 580 /* Set the saved left-margin. */ 581 mt->lmargin = (size_t)ival; 582 583 /* Don't print the length value. */ 584 for (nn = n->child; nn->next; nn = nn->next) 585 print_node(p, mt, nn, m); 586 return(0); 587 case (MAN_BODY): 588 p->offset = mt->offset + len; 589 p->rmargin = p->maxrmargin; 590 break; 591 default: 592 break; 593 } 594 595 return(1); 596 } 597 598 599 /* ARGSUSED */ 600 static void 601 post_IP(DECL_ARGS) 602 { 603 604 switch (n->type) { 605 case (MAN_HEAD): 606 term_flushln(p); 607 p->flags &= ~TERMP_NOBREAK; 608 p->flags &= ~TERMP_TWOSPACE; 609 p->rmargin = p->maxrmargin; 610 break; 611 case (MAN_BODY): 612 term_flushln(p); 613 p->flags &= ~TERMP_NOLPAD; 614 break; 615 default: 616 break; 617 } 618 } 619 620 621 /* ARGSUSED */ 622 static int 623 pre_TP(DECL_ARGS) 624 { 625 const struct man_node *nn; 626 size_t len; 627 int ival; 628 629 switch (n->type) { 630 case (MAN_HEAD): 631 p->flags |= TERMP_NOBREAK; 632 p->flags |= TERMP_TWOSPACE; 633 break; 634 case (MAN_BODY): 635 p->flags |= TERMP_NOLPAD; 636 p->flags |= TERMP_NOSPACE; 637 break; 638 case (MAN_BLOCK): 639 fmt_block_vspace(p, n); 640 /* FALLTHROUGH */ 641 default: 642 return(1); 643 } 644 645 len = (size_t)mt->lmargin; 646 ival = -1; 647 648 /* Calculate offset. */ 649 650 if (NULL != (nn = n->parent->head->child)) 651 if (NULL != nn->next) 652 if ((ival = arg_width(nn)) >= 0) 653 len = (size_t)ival; 654 655 switch (n->type) { 656 case (MAN_HEAD): 657 /* Handle zero-length properly. */ 658 if (0 == len) 659 len = 1; 660 661 p->offset = mt->offset; 662 p->rmargin = mt->offset + len; 663 664 /* Don't print same-line elements. */ 665 for (nn = n->child; nn; nn = nn->next) 666 if (nn->line > n->line) 667 print_node(p, mt, nn, m); 668 669 if (ival >= 0) 670 mt->lmargin = (size_t)ival; 671 672 return(0); 673 case (MAN_BODY): 674 p->offset = mt->offset + len; 675 p->rmargin = p->maxrmargin; 676 break; 677 default: 678 break; 679 } 680 681 return(1); 682 } 683 684 685 /* ARGSUSED */ 686 static void 687 post_TP(DECL_ARGS) 688 { 689 690 switch (n->type) { 691 case (MAN_HEAD): 692 term_flushln(p); 693 p->flags &= ~TERMP_NOBREAK; 694 p->flags &= ~TERMP_TWOSPACE; 695 p->rmargin = p->maxrmargin; 696 break; 697 case (MAN_BODY): 698 term_flushln(p); 699 p->flags &= ~TERMP_NOLPAD; 700 break; 701 default: 702 break; 703 } 704 } 705 706 707 /* ARGSUSED */ 708 static int 709 pre_SS(DECL_ARGS) 710 { 711 712 switch (n->type) { 713 case (MAN_BLOCK): 714 mt->lmargin = INDENT; 715 mt->offset = INDENT; 716 /* If following a prior empty `SS', no vspace. */ 717 if (n->prev && MAN_SS == n->prev->tok) 718 if (NULL == n->prev->body->child) 719 break; 720 if (NULL == n->prev) 721 break; 722 term_vspace(p); 723 break; 724 case (MAN_HEAD): 725 p->bold++; 726 p->offset = HALFINDENT; 727 break; 728 case (MAN_BODY): 729 p->offset = mt->offset; 730 break; 731 default: 732 break; 733 } 734 735 return(1); 736 } 737 738 739 /* ARGSUSED */ 740 static void 741 post_SS(DECL_ARGS) 742 { 743 744 switch (n->type) { 745 case (MAN_HEAD): 746 term_newln(p); 747 p->bold--; 748 break; 749 case (MAN_BODY): 750 term_newln(p); 751 break; 752 default: 753 break; 754 } 755 } 756 757 758 /* ARGSUSED */ 759 static int 760 pre_SH(DECL_ARGS) 761 { 762 763 switch (n->type) { 764 case (MAN_BLOCK): 765 mt->lmargin = INDENT; 766 mt->offset = INDENT; 767 /* If following a prior empty `SH', no vspace. */ 768 if (n->prev && MAN_SH == n->prev->tok) 769 if (NULL == n->prev->body->child) 770 break; 771 term_vspace(p); 772 break; 773 case (MAN_HEAD): 774 p->bold++; 775 p->offset = 0; 776 break; 777 case (MAN_BODY): 778 p->offset = mt->offset; 779 break; 780 default: 781 break; 782 } 783 784 return(1); 785 } 786 787 788 /* ARGSUSED */ 789 static void 790 post_SH(DECL_ARGS) 791 { 792 793 switch (n->type) { 794 case (MAN_HEAD): 795 term_newln(p); 796 p->bold--; 797 break; 798 case (MAN_BODY): 799 term_newln(p); 800 break; 801 default: 802 break; 803 } 804 } 805 806 807 /* ARGSUSED */ 808 static int 809 pre_RS(DECL_ARGS) 810 { 811 const struct man_node *nn; 812 int ival; 813 814 switch (n->type) { 815 case (MAN_BLOCK): 816 term_newln(p); 817 return(1); 818 case (MAN_HEAD): 819 return(0); 820 default: 821 break; 822 } 823 824 if (NULL == (nn = n->parent->head->child)) { 825 mt->offset = mt->lmargin + INDENT; 826 p->offset = mt->offset; 827 return(1); 828 } 829 830 if ((ival = arg_width(nn)) < 0) 831 return(1); 832 833 mt->offset = INDENT + (size_t)ival; 834 p->offset = mt->offset; 835 836 return(1); 837 } 838 839 840 /* ARGSUSED */ 841 static void 842 post_RS(DECL_ARGS) 843 { 844 845 switch (n->type) { 846 case (MAN_BLOCK): 847 mt->offset = mt->lmargin = INDENT; 848 break; 849 default: 850 term_newln(p); 851 p->offset = INDENT; 852 break; 853 } 854 } 855 856 857 static void 858 print_node(DECL_ARGS) 859 { 860 int c, sz; 861 862 c = 1; 863 864 switch (n->type) { 865 case(MAN_TEXT): 866 if (0 == *n->string) { 867 term_vspace(p); 868 break; 869 } 870 /* 871 * Note! This is hacky. Here, we recognise the `\c' 872 * escape embedded in so many -man pages. It's supposed 873 * to remove the subsequent space, so we mark NOSPACE if 874 * it's encountered in the string. 875 */ 876 sz = (int)strlen(n->string); 877 term_word(p, n->string); 878 if (sz >= 2 && n->string[sz - 1] == 'c' && 879 n->string[sz - 2] == '\\') 880 p->flags |= TERMP_NOSPACE; 881 /* FIXME: this means that macro lines are munged! */ 882 if (MANT_LITERAL & mt->fl) { 883 p->flags |= TERMP_NOSPACE; 884 term_flushln(p); 885 } 886 break; 887 default: 888 if (termacts[n->tok].pre) 889 c = (*termacts[n->tok].pre)(p, mt, n, m); 890 break; 891 } 892 893 if (c && n->child) 894 print_body(p, mt, n->child, m); 895 896 if (MAN_TEXT != n->type) 897 if (termacts[n->tok].post) 898 (*termacts[n->tok].post)(p, mt, n, m); 899 } 900 901 902 static void 903 print_body(DECL_ARGS) 904 { 905 906 print_node(p, mt, n, m); 907 if ( ! n->next) 908 return; 909 print_body(p, mt, n->next, m); 910 } 911 912 913 static void 914 print_foot(struct termp *p, const struct man_meta *meta) 915 { 916 struct tm *tm; 917 char *buf; 918 919 if (NULL == (buf = malloc(p->rmargin))) 920 err(EXIT_FAILURE, "malloc"); 921 922 tm = localtime(&meta->date); 923 924 if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm)) 925 err(EXIT_FAILURE, "strftime"); 926 927 term_vspace(p); 928 929 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; 930 p->rmargin = p->maxrmargin - strlen(buf); 931 p->offset = 0; 932 933 if (meta->source) 934 term_word(p, meta->source); 935 if (meta->source) 936 term_word(p, ""); 937 term_flushln(p); 938 939 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; 940 p->offset = p->rmargin; 941 p->rmargin = p->maxrmargin; 942 p->flags &= ~TERMP_NOBREAK; 943 944 term_word(p, buf); 945 term_flushln(p); 946 947 free(buf); 948 } 949 950 951 static void 952 print_head(struct termp *p, const struct man_meta *meta) 953 { 954 char *buf, *title; 955 956 p->rmargin = p->maxrmargin; 957 p->offset = 0; 958 959 if (NULL == (buf = malloc(p->rmargin))) 960 err(EXIT_FAILURE, "malloc"); 961 if (NULL == (title = malloc(p->rmargin))) 962 err(EXIT_FAILURE, "malloc"); 963 964 if (meta->vol) 965 (void)strlcpy(buf, meta->vol, p->rmargin); 966 else 967 *buf = 0; 968 969 (void)snprintf(title, p->rmargin, "%s(%d)", 970 meta->title, meta->msec); 971 972 p->offset = 0; 973 p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2; 974 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; 975 976 term_word(p, title); 977 term_flushln(p); 978 979 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; 980 p->offset = p->rmargin; 981 p->rmargin = p->maxrmargin - strlen(title); 982 983 term_word(p, buf); 984 term_flushln(p); 985 986 p->offset = p->rmargin; 987 p->rmargin = p->maxrmargin; 988 p->flags &= ~TERMP_NOBREAK; 989 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; 990 991 term_word(p, title); 992 term_flushln(p); 993 994 p->rmargin = p->maxrmargin; 995 p->offset = 0; 996 p->flags &= ~TERMP_NOSPACE; 997 998 free(title); 999 free(buf); 1000 } 1001 1002