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