1 /* $Id: man_term.c,v 1.209 2017/07/31 15:19:06 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 "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->tok == MAN_SS && n->body->child == NULL)) 679 break; 680 681 for (i = 0; i < mt->pardist; i++) 682 term_vspace(p); 683 break; 684 case ROFFT_HEAD: 685 term_fontrepl(p, TERMFONT_BOLD); 686 p->tcol->offset = term_len(p, 3); 687 p->tcol->rmargin = mt->offset; 688 p->trailspace = mt->offset; 689 p->flags |= TERMP_NOBREAK | TERMP_BRIND; 690 break; 691 case ROFFT_BODY: 692 p->tcol->offset = mt->offset; 693 p->tcol->rmargin = p->maxrmargin; 694 p->trailspace = 0; 695 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND); 696 break; 697 default: 698 break; 699 } 700 701 return 1; 702 } 703 704 static void 705 post_SS(DECL_ARGS) 706 { 707 708 switch (n->type) { 709 case ROFFT_HEAD: 710 term_newln(p); 711 break; 712 case ROFFT_BODY: 713 term_newln(p); 714 break; 715 default: 716 break; 717 } 718 } 719 720 static int 721 pre_SH(DECL_ARGS) 722 { 723 int i; 724 725 switch (n->type) { 726 case ROFFT_BLOCK: 727 mt->fl &= ~MANT_LITERAL; 728 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent); 729 mt->offset = term_len(p, p->defindent); 730 731 /* 732 * No vertical space before the first section 733 * and after an empty section. 734 */ 735 736 do { 737 n = n->prev; 738 } while (n != NULL && n->tok >= MAN_TH && 739 termacts[n->tok].flags & MAN_NOTEXT); 740 if (n == NULL || (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 889 case ROFFT_EQN: 890 if ( ! (n->flags & NODE_LINE)) 891 p->flags |= TERMP_NOSPACE; 892 term_eqn(p, n->eqn); 893 if (n->next != NULL && ! (n->next->flags & NODE_LINE)) 894 p->flags |= TERMP_NOSPACE; 895 return; 896 case ROFFT_TBL: 897 if (p->tbl.cols == NULL) 898 term_vspace(p); 899 term_tbl(p, n->span); 900 return; 901 default: 902 break; 903 } 904 905 if (n->tok < ROFF_MAX) { 906 roff_term_pre(p, n); 907 return; 908 } 909 910 assert(n->tok >= MAN_TH && n->tok <= MAN_MAX); 911 if ( ! (MAN_NOTEXT & termacts[n->tok].flags)) 912 term_fontrepl(p, TERMFONT_NONE); 913 914 c = 1; 915 if (termacts[n->tok].pre) 916 c = (*termacts[n->tok].pre)(p, mt, n, meta); 917 918 if (c && n->child) 919 print_man_nodelist(p, mt, n->child, meta); 920 921 if (termacts[n->tok].post) 922 (*termacts[n->tok].post)(p, mt, n, meta); 923 if ( ! (MAN_NOTEXT & termacts[n->tok].flags)) 924 term_fontrepl(p, TERMFONT_NONE); 925 926 out: 927 /* 928 * If we're in a literal context, make sure that words 929 * together on the same line stay together. This is a 930 * POST-printing call, so we check the NEXT word. Since 931 * -man doesn't have nested macros, we don't need to be 932 * more specific than this. 933 */ 934 if (mt->fl & MANT_LITERAL && 935 ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) && 936 (n->next == NULL || n->next->flags & NODE_LINE)) { 937 p->flags |= TERMP_BRNEVER | TERMP_NOSPACE; 938 if (n->string != NULL && *n->string != '\0') 939 term_flushln(p); 940 else 941 term_newln(p); 942 p->flags &= ~TERMP_BRNEVER; 943 if (p->tcol->rmargin < p->maxrmargin && 944 n->parent->tok == MAN_HP) { 945 p->tcol->offset = p->tcol->rmargin; 946 p->tcol->rmargin = p->maxrmargin; 947 } 948 } 949 if (NODE_EOS & n->flags) 950 p->flags |= TERMP_SENTENCE; 951 } 952 953 954 static void 955 print_man_nodelist(DECL_ARGS) 956 { 957 958 while (n != NULL) { 959 print_man_node(p, mt, n, meta); 960 n = n->next; 961 } 962 } 963 964 static void 965 print_man_foot(struct termp *p, const struct roff_meta *meta) 966 { 967 char *title; 968 size_t datelen, titlen; 969 970 assert(meta->title); 971 assert(meta->msec); 972 assert(meta->date); 973 974 term_fontrepl(p, TERMFONT_NONE); 975 976 if (meta->hasbody) 977 term_vspace(p); 978 979 /* 980 * Temporary, undocumented option to imitate mdoc(7) output. 981 * In the bottom right corner, use the operating system 982 * instead of the title. 983 */ 984 985 if ( ! p->mdocstyle) { 986 if (meta->hasbody) { 987 term_vspace(p); 988 term_vspace(p); 989 } 990 mandoc_asprintf(&title, "%s(%s)", 991 meta->title, meta->msec); 992 } else if (meta->os) { 993 title = mandoc_strdup(meta->os); 994 } else { 995 title = mandoc_strdup(""); 996 } 997 datelen = term_strlen(p, meta->date); 998 999 /* Bottom left corner: operating system. */ 1000 1001 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; 1002 p->trailspace = 1; 1003 p->tcol->offset = 0; 1004 p->tcol->rmargin = p->maxrmargin > datelen ? 1005 (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0; 1006 1007 if (meta->os) 1008 term_word(p, meta->os); 1009 term_flushln(p); 1010 1011 /* At the bottom in the middle: manual date. */ 1012 1013 p->tcol->offset = p->tcol->rmargin; 1014 titlen = term_strlen(p, title); 1015 p->tcol->rmargin = p->maxrmargin > titlen ? 1016 p->maxrmargin - titlen : 0; 1017 p->flags |= TERMP_NOSPACE; 1018 1019 term_word(p, meta->date); 1020 term_flushln(p); 1021 1022 /* Bottom right corner: manual title and section. */ 1023 1024 p->flags &= ~TERMP_NOBREAK; 1025 p->flags |= TERMP_NOSPACE; 1026 p->trailspace = 0; 1027 p->tcol->offset = p->tcol->rmargin; 1028 p->tcol->rmargin = p->maxrmargin; 1029 1030 term_word(p, title); 1031 term_flushln(p); 1032 free(title); 1033 } 1034 1035 static void 1036 print_man_head(struct termp *p, const struct roff_meta *meta) 1037 { 1038 const char *volume; 1039 char *title; 1040 size_t vollen, titlen; 1041 1042 assert(meta->title); 1043 assert(meta->msec); 1044 1045 volume = NULL == meta->vol ? "" : meta->vol; 1046 vollen = term_strlen(p, volume); 1047 1048 /* Top left corner: manual title and section. */ 1049 1050 mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec); 1051 titlen = term_strlen(p, title); 1052 1053 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; 1054 p->trailspace = 1; 1055 p->tcol->offset = 0; 1056 p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ? 1057 (p->maxrmargin - vollen + term_len(p, 1)) / 2 : 1058 vollen < p->maxrmargin ? p->maxrmargin - vollen : 0; 1059 1060 term_word(p, title); 1061 term_flushln(p); 1062 1063 /* At the top in the middle: manual volume. */ 1064 1065 p->flags |= TERMP_NOSPACE; 1066 p->tcol->offset = p->tcol->rmargin; 1067 p->tcol->rmargin = p->tcol->offset + vollen + titlen < 1068 p->maxrmargin ? p->maxrmargin - titlen : p->maxrmargin; 1069 1070 term_word(p, volume); 1071 term_flushln(p); 1072 1073 /* Top right corner: title and section, again. */ 1074 1075 p->flags &= ~TERMP_NOBREAK; 1076 p->trailspace = 0; 1077 if (p->tcol->rmargin + titlen <= p->maxrmargin) { 1078 p->flags |= TERMP_NOSPACE; 1079 p->tcol->offset = p->tcol->rmargin; 1080 p->tcol->rmargin = p->maxrmargin; 1081 term_word(p, title); 1082 term_flushln(p); 1083 } 1084 1085 p->flags &= ~TERMP_NOSPACE; 1086 p->tcol->offset = 0; 1087 p->tcol->rmargin = p->maxrmargin; 1088 1089 /* 1090 * Groff prints three blank lines before the content. 1091 * Do the same, except in the temporary, undocumented 1092 * mode imitating mdoc(7) output. 1093 */ 1094 1095 term_vspace(p); 1096 if ( ! p->mdocstyle) { 1097 term_vspace(p); 1098 term_vspace(p); 1099 } 1100 free(title); 1101 } 1102