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