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