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