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