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