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