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