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