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