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