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