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