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