1 /* $OpenBSD: man_term.c,v 1.162 2017/07/31 15:18:59 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2010-2015, 2017 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->tok == MAN_SS && n->body->child == NULL)) 677 break; 678 679 for (i = 0; i < mt->pardist; i++) 680 term_vspace(p); 681 break; 682 case ROFFT_HEAD: 683 term_fontrepl(p, TERMFONT_BOLD); 684 p->tcol->offset = term_len(p, 3); 685 p->tcol->rmargin = mt->offset; 686 p->trailspace = mt->offset; 687 p->flags |= TERMP_NOBREAK | TERMP_BRIND; 688 break; 689 case ROFFT_BODY: 690 p->tcol->offset = mt->offset; 691 p->tcol->rmargin = p->maxrmargin; 692 p->trailspace = 0; 693 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND); 694 break; 695 default: 696 break; 697 } 698 699 return 1; 700 } 701 702 static void 703 post_SS(DECL_ARGS) 704 { 705 706 switch (n->type) { 707 case ROFFT_HEAD: 708 term_newln(p); 709 break; 710 case ROFFT_BODY: 711 term_newln(p); 712 break; 713 default: 714 break; 715 } 716 } 717 718 static int 719 pre_SH(DECL_ARGS) 720 { 721 int i; 722 723 switch (n->type) { 724 case ROFFT_BLOCK: 725 mt->fl &= ~MANT_LITERAL; 726 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent); 727 mt->offset = term_len(p, p->defindent); 728 729 /* 730 * No vertical space before the first section 731 * and after an empty section. 732 */ 733 734 do { 735 n = n->prev; 736 } while (n != NULL && n->tok >= MAN_TH && 737 termacts[n->tok].flags & MAN_NOTEXT); 738 if (n == NULL || (n->tok == MAN_SH && n->body->child == NULL)) 739 break; 740 741 for (i = 0; i < mt->pardist; i++) 742 term_vspace(p); 743 break; 744 case ROFFT_HEAD: 745 term_fontrepl(p, TERMFONT_BOLD); 746 p->tcol->offset = 0; 747 p->tcol->rmargin = mt->offset; 748 p->trailspace = mt->offset; 749 p->flags |= TERMP_NOBREAK | TERMP_BRIND; 750 break; 751 case ROFFT_BODY: 752 p->tcol->offset = mt->offset; 753 p->tcol->rmargin = p->maxrmargin; 754 p->trailspace = 0; 755 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND); 756 break; 757 default: 758 break; 759 } 760 761 return 1; 762 } 763 764 static void 765 post_SH(DECL_ARGS) 766 { 767 768 switch (n->type) { 769 case ROFFT_HEAD: 770 term_newln(p); 771 break; 772 case ROFFT_BODY: 773 term_newln(p); 774 break; 775 default: 776 break; 777 } 778 } 779 780 static int 781 pre_RS(DECL_ARGS) 782 { 783 struct roffsu su; 784 785 switch (n->type) { 786 case ROFFT_BLOCK: 787 term_newln(p); 788 return 1; 789 case ROFFT_HEAD: 790 return 0; 791 default: 792 break; 793 } 794 795 n = n->parent->head; 796 n->aux = SHRT_MAX + 1; 797 if (n->child == NULL) 798 n->aux = mt->lmargin[mt->lmargincur]; 799 else if (a2roffsu(n->child->string, &su, SCALE_EN) != NULL) 800 n->aux = term_hen(p, &su); 801 if (n->aux < 0 && (size_t)(-n->aux) > mt->offset) 802 n->aux = -mt->offset; 803 else if (n->aux > SHRT_MAX) 804 n->aux = term_len(p, p->defindent); 805 806 mt->offset += n->aux; 807 p->tcol->offset = mt->offset; 808 p->tcol->rmargin = p->maxrmargin; 809 810 if (++mt->lmarginsz < MAXMARGINS) 811 mt->lmargincur = mt->lmarginsz; 812 813 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent); 814 return 1; 815 } 816 817 static void 818 post_RS(DECL_ARGS) 819 { 820 821 switch (n->type) { 822 case ROFFT_BLOCK: 823 return; 824 case ROFFT_HEAD: 825 return; 826 default: 827 term_newln(p); 828 break; 829 } 830 831 mt->offset -= n->parent->head->aux; 832 p->tcol->offset = mt->offset; 833 834 if (--mt->lmarginsz < MAXMARGINS) 835 mt->lmargincur = mt->lmarginsz; 836 } 837 838 static int 839 pre_UR(DECL_ARGS) 840 { 841 842 return n->type != ROFFT_HEAD; 843 } 844 845 static void 846 post_UR(DECL_ARGS) 847 { 848 849 if (n->type != ROFFT_BLOCK) 850 return; 851 852 term_word(p, "<"); 853 p->flags |= TERMP_NOSPACE; 854 855 if (NULL != n->child->child) 856 print_man_node(p, mt, n->child->child, meta); 857 858 p->flags |= TERMP_NOSPACE; 859 term_word(p, ">"); 860 } 861 862 static void 863 print_man_node(DECL_ARGS) 864 { 865 int c; 866 867 switch (n->type) { 868 case ROFFT_TEXT: 869 /* 870 * If we have a blank line, output a vertical space. 871 * If we have a space as the first character, break 872 * before printing the line's data. 873 */ 874 if (*n->string == '\0') { 875 if (p->flags & TERMP_NONEWLINE) 876 term_newln(p); 877 else 878 term_vspace(p); 879 return; 880 } else if (*n->string == ' ' && n->flags & NODE_LINE && 881 (p->flags & TERMP_NONEWLINE) == 0) 882 term_newln(p); 883 884 term_word(p, n->string); 885 goto out; 886 887 case ROFFT_EQN: 888 if ( ! (n->flags & NODE_LINE)) 889 p->flags |= TERMP_NOSPACE; 890 term_eqn(p, n->eqn); 891 if (n->next != NULL && ! (n->next->flags & NODE_LINE)) 892 p->flags |= TERMP_NOSPACE; 893 return; 894 case ROFFT_TBL: 895 if (p->tbl.cols == NULL) 896 term_vspace(p); 897 term_tbl(p, n->span); 898 return; 899 default: 900 break; 901 } 902 903 if (n->tok < ROFF_MAX) { 904 roff_term_pre(p, n); 905 return; 906 } 907 908 assert(n->tok >= MAN_TH && n->tok <= MAN_MAX); 909 if ( ! (MAN_NOTEXT & termacts[n->tok].flags)) 910 term_fontrepl(p, TERMFONT_NONE); 911 912 c = 1; 913 if (termacts[n->tok].pre) 914 c = (*termacts[n->tok].pre)(p, mt, n, meta); 915 916 if (c && n->child) 917 print_man_nodelist(p, mt, n->child, meta); 918 919 if (termacts[n->tok].post) 920 (*termacts[n->tok].post)(p, mt, n, meta); 921 if ( ! (MAN_NOTEXT & termacts[n->tok].flags)) 922 term_fontrepl(p, TERMFONT_NONE); 923 924 out: 925 /* 926 * If we're in a literal context, make sure that words 927 * together on the same line stay together. This is a 928 * POST-printing call, so we check the NEXT word. Since 929 * -man doesn't have nested macros, we don't need to be 930 * more specific than this. 931 */ 932 if (mt->fl & MANT_LITERAL && 933 ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) && 934 (n->next == NULL || n->next->flags & NODE_LINE)) { 935 p->flags |= TERMP_BRNEVER | TERMP_NOSPACE; 936 if (n->string != NULL && *n->string != '\0') 937 term_flushln(p); 938 else 939 term_newln(p); 940 p->flags &= ~TERMP_BRNEVER; 941 if (p->tcol->rmargin < p->maxrmargin && 942 n->parent->tok == MAN_HP) { 943 p->tcol->offset = p->tcol->rmargin; 944 p->tcol->rmargin = p->maxrmargin; 945 } 946 } 947 if (NODE_EOS & n->flags) 948 p->flags |= TERMP_SENTENCE; 949 } 950 951 952 static void 953 print_man_nodelist(DECL_ARGS) 954 { 955 956 while (n != NULL) { 957 print_man_node(p, mt, n, meta); 958 n = n->next; 959 } 960 } 961 962 static void 963 print_man_foot(struct termp *p, const struct roff_meta *meta) 964 { 965 char *title; 966 size_t datelen, titlen; 967 968 assert(meta->title); 969 assert(meta->msec); 970 assert(meta->date); 971 972 term_fontrepl(p, TERMFONT_NONE); 973 974 if (meta->hasbody) 975 term_vspace(p); 976 977 /* 978 * Temporary, undocumented option to imitate mdoc(7) output. 979 * In the bottom right corner, use the operating system 980 * instead of the title. 981 */ 982 983 if ( ! p->mdocstyle) { 984 if (meta->hasbody) { 985 term_vspace(p); 986 term_vspace(p); 987 } 988 mandoc_asprintf(&title, "%s(%s)", 989 meta->title, meta->msec); 990 } else if (meta->os) { 991 title = mandoc_strdup(meta->os); 992 } else { 993 title = mandoc_strdup(""); 994 } 995 datelen = term_strlen(p, meta->date); 996 997 /* Bottom left corner: operating system. */ 998 999 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; 1000 p->trailspace = 1; 1001 p->tcol->offset = 0; 1002 p->tcol->rmargin = p->maxrmargin > datelen ? 1003 (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0; 1004 1005 if (meta->os) 1006 term_word(p, meta->os); 1007 term_flushln(p); 1008 1009 /* At the bottom in the middle: manual date. */ 1010 1011 p->tcol->offset = p->tcol->rmargin; 1012 titlen = term_strlen(p, title); 1013 p->tcol->rmargin = p->maxrmargin > titlen ? 1014 p->maxrmargin - titlen : 0; 1015 p->flags |= TERMP_NOSPACE; 1016 1017 term_word(p, meta->date); 1018 term_flushln(p); 1019 1020 /* Bottom right corner: manual title and section. */ 1021 1022 p->flags &= ~TERMP_NOBREAK; 1023 p->flags |= TERMP_NOSPACE; 1024 p->trailspace = 0; 1025 p->tcol->offset = p->tcol->rmargin; 1026 p->tcol->rmargin = p->maxrmargin; 1027 1028 term_word(p, title); 1029 term_flushln(p); 1030 free(title); 1031 } 1032 1033 static void 1034 print_man_head(struct termp *p, const struct roff_meta *meta) 1035 { 1036 const char *volume; 1037 char *title; 1038 size_t vollen, titlen; 1039 1040 assert(meta->title); 1041 assert(meta->msec); 1042 1043 volume = NULL == meta->vol ? "" : meta->vol; 1044 vollen = term_strlen(p, volume); 1045 1046 /* Top left corner: manual title and section. */ 1047 1048 mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec); 1049 titlen = term_strlen(p, title); 1050 1051 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; 1052 p->trailspace = 1; 1053 p->tcol->offset = 0; 1054 p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ? 1055 (p->maxrmargin - vollen + term_len(p, 1)) / 2 : 1056 vollen < p->maxrmargin ? p->maxrmargin - vollen : 0; 1057 1058 term_word(p, title); 1059 term_flushln(p); 1060 1061 /* At the top in the middle: manual volume. */ 1062 1063 p->flags |= TERMP_NOSPACE; 1064 p->tcol->offset = p->tcol->rmargin; 1065 p->tcol->rmargin = p->tcol->offset + vollen + titlen < 1066 p->maxrmargin ? p->maxrmargin - titlen : p->maxrmargin; 1067 1068 term_word(p, volume); 1069 term_flushln(p); 1070 1071 /* Top right corner: title and section, again. */ 1072 1073 p->flags &= ~TERMP_NOBREAK; 1074 p->trailspace = 0; 1075 if (p->tcol->rmargin + titlen <= p->maxrmargin) { 1076 p->flags |= TERMP_NOSPACE; 1077 p->tcol->offset = p->tcol->rmargin; 1078 p->tcol->rmargin = p->maxrmargin; 1079 term_word(p, title); 1080 term_flushln(p); 1081 } 1082 1083 p->flags &= ~TERMP_NOSPACE; 1084 p->tcol->offset = 0; 1085 p->tcol->rmargin = p->maxrmargin; 1086 1087 /* 1088 * Groff prints three blank lines before the content. 1089 * Do the same, except in the temporary, undocumented 1090 * mode imitating mdoc(7) output. 1091 */ 1092 1093 term_vspace(p); 1094 if ( ! p->mdocstyle) { 1095 term_vspace(p); 1096 term_vspace(p); 1097 } 1098 free(title); 1099 } 1100