1 /* $Id: man_term.c,v 1.248 2025/07/27 15:27:28 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2010-2020,2022-23,2025 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 in basic units. */ 45 int lmargincur; /* Index of current margin. */ 46 int lmarginsz; /* Actual number of nested margins. */ 47 size_t offset; /* Default offset in basic units. */ 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 * Print leading vertical space before a paragraph, unless 198 * it is the first paragraph in a section or subsection. 199 * If it is the first paragraph in an .RS block, consider 200 * that .RS block instead of the paragraph, recursively. 201 */ 202 static void 203 print_bvspace(struct termp *p, struct roff_node *n, int pardist) 204 { 205 struct roff_node *nch; 206 int i; 207 208 term_newln(p); 209 210 if (n->body != NULL && 211 (nch = roff_node_child(n->body)) != NULL && 212 nch->type == ROFFT_TBL) 213 return; 214 215 while (roff_node_prev(n) == NULL) { 216 n = n->parent; 217 if (n->tok != MAN_RS) 218 return; 219 if (n->type == ROFFT_BODY) 220 n = n->parent; 221 } 222 for (i = 0; i < pardist; i++) 223 term_vspace(p); 224 } 225 226 static int 227 pre_ign(DECL_ARGS) 228 { 229 return 0; 230 } 231 232 static int 233 pre_I(DECL_ARGS) 234 { 235 term_fontrepl(p, TERMFONT_UNDER); 236 return 1; 237 } 238 239 static int 240 pre_literal(DECL_ARGS) 241 { 242 term_newln(p); 243 244 /* 245 * Unlike .IP and .TP, .HP does not have a HEAD. 246 * So in case a second call to term_flushln() is needed, 247 * indentation has to be set up explicitly. 248 */ 249 if (n->parent->tok == MAN_HP && p->tcol->rmargin < p->maxrmargin) { 250 p->tcol->offset = p->tcol->rmargin; 251 p->tcol->rmargin = p->maxrmargin; 252 p->trailspace = 0; 253 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND); 254 p->flags |= TERMP_NOSPACE; 255 } 256 return 0; 257 } 258 259 static int 260 pre_PD(DECL_ARGS) 261 { 262 struct roffsu su; 263 264 n = n->child; 265 if (n == NULL) { 266 mt->pardist = 1; 267 return 0; 268 } 269 assert(n->type == ROFFT_TEXT); 270 if (a2roffsu(n->string, &su, SCALE_VS) != NULL) 271 mt->pardist = term_vspan(p, &su); 272 return 0; 273 } 274 275 static int 276 pre_alternate(DECL_ARGS) 277 { 278 enum termfont font[2]; 279 struct roff_node *nn; 280 int i; 281 282 switch (n->tok) { 283 case MAN_RB: 284 font[0] = TERMFONT_NONE; 285 font[1] = TERMFONT_BOLD; 286 break; 287 case MAN_RI: 288 font[0] = TERMFONT_NONE; 289 font[1] = TERMFONT_UNDER; 290 break; 291 case MAN_BR: 292 font[0] = TERMFONT_BOLD; 293 font[1] = TERMFONT_NONE; 294 break; 295 case MAN_BI: 296 font[0] = TERMFONT_BOLD; 297 font[1] = TERMFONT_UNDER; 298 break; 299 case MAN_IR: 300 font[0] = TERMFONT_UNDER; 301 font[1] = TERMFONT_NONE; 302 break; 303 case MAN_IB: 304 font[0] = TERMFONT_UNDER; 305 font[1] = TERMFONT_BOLD; 306 break; 307 default: 308 abort(); 309 } 310 for (i = 0, nn = n->child; nn != NULL; nn = nn->next, i = 1 - i) { 311 term_fontrepl(p, font[i]); 312 assert(nn->type == ROFFT_TEXT); 313 term_word(p, nn->string); 314 if (nn->flags & NODE_EOS) 315 p->flags |= TERMP_SENTENCE; 316 if (nn->next != NULL) 317 p->flags |= TERMP_NOSPACE; 318 } 319 return 0; 320 } 321 322 static int 323 pre_B(DECL_ARGS) 324 { 325 term_fontrepl(p, TERMFONT_BOLD); 326 return 1; 327 } 328 329 static int 330 pre_MR(DECL_ARGS) 331 { 332 term_fontrepl(p, TERMFONT_NONE); 333 n = n->child; 334 if (n != NULL) { 335 term_word(p, n->string); /* name */ 336 p->flags |= TERMP_NOSPACE; 337 } 338 term_word(p, "("); 339 p->flags |= TERMP_NOSPACE; 340 if (n != NULL && (n = n->next) != NULL) { 341 term_word(p, n->string); /* section */ 342 p->flags |= TERMP_NOSPACE; 343 } 344 term_word(p, ")"); 345 if (n != NULL && (n = n->next) != NULL) { 346 p->flags |= TERMP_NOSPACE; 347 term_word(p, n->string); /* suffix */ 348 } 349 return 0; 350 } 351 352 static int 353 pre_OP(DECL_ARGS) 354 { 355 term_word(p, "["); 356 p->flags |= TERMP_KEEP | TERMP_NOSPACE; 357 358 if ((n = n->child) != NULL) { 359 term_fontrepl(p, TERMFONT_BOLD); 360 term_word(p, n->string); 361 } 362 if (n != NULL && n->next != NULL) { 363 term_fontrepl(p, TERMFONT_UNDER); 364 term_word(p, n->next->string); 365 } 366 term_fontrepl(p, TERMFONT_NONE); 367 p->flags &= ~TERMP_KEEP; 368 p->flags |= TERMP_NOSPACE; 369 term_word(p, "]"); 370 return 0; 371 } 372 373 static int 374 pre_in(DECL_ARGS) 375 { 376 struct roffsu su; 377 const char *cp; /* Request argument. */ 378 size_t v; /* Indentation in basic units. */ 379 int less; 380 381 term_newln(p); 382 383 if (n->child == NULL) { 384 p->tcol->offset = mt->offset; 385 return 0; 386 } 387 388 cp = n->child->string; 389 less = 0; 390 391 if (*cp == '-') { 392 less = -1; 393 cp++; 394 } else if (*cp == '+') { 395 less = 1; 396 cp++; 397 } 398 399 if (a2roffsu(cp, &su, SCALE_EN) == NULL) 400 return 0; 401 402 v = term_hspan(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; /* Indentation in basic units. */ 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_hspan(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, 7); 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; /* Indentation in basic units. */ 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_hspan(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; /* Indentation in basic units. */ 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_hspan(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, 7); 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 p->fontibi = 1; 698 term_fontrepl(p, TERMFONT_BOLD); 699 p->tcol->offset = term_len(p, p->defindent) / 2 + 1; 700 p->tcol->rmargin = mt->offset; 701 p->trailspace = mt->offset / term_len(p, 1); 702 p->flags |= TERMP_NOBREAK | TERMP_BRIND; 703 break; 704 case ROFFT_BODY: 705 p->tcol->offset = mt->offset; 706 p->tcol->rmargin = p->maxrmargin; 707 p->trailspace = 0; 708 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND); 709 break; 710 default: 711 break; 712 } 713 return 1; 714 } 715 716 static int 717 pre_SH(DECL_ARGS) 718 { 719 int i; 720 721 switch (n->type) { 722 case ROFFT_BLOCK: 723 mt->lmargin[mt->lmargincur] = term_len(p, 7); 724 mt->offset = term_len(p, p->defindent); 725 726 /* 727 * No vertical space before the first section 728 * and after an empty section. 729 */ 730 731 if ((n = roff_node_prev(n)) == NULL || 732 (n->tok == MAN_SH && roff_node_child(n->body) == NULL)) 733 break; 734 735 for (i = 0; i < mt->pardist; i++) 736 term_vspace(p); 737 break; 738 case ROFFT_HEAD: 739 p->fontibi = 1; 740 term_fontrepl(p, TERMFONT_BOLD); 741 p->tcol->offset = 0; 742 p->tcol->rmargin = mt->offset; 743 p->trailspace = mt->offset / term_len(p, 1); 744 p->flags |= TERMP_NOBREAK | TERMP_BRIND; 745 break; 746 case ROFFT_BODY: 747 p->tcol->offset = mt->offset; 748 p->tcol->rmargin = p->maxrmargin; 749 p->trailspace = 0; 750 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND); 751 break; 752 default: 753 abort(); 754 } 755 return 1; 756 } 757 758 static void 759 post_SH(DECL_ARGS) 760 { 761 switch (n->type) { 762 case ROFFT_BLOCK: 763 break; 764 case ROFFT_HEAD: 765 p->fontibi = 0; 766 /* FALLTHROUGH */ 767 case ROFFT_BODY: 768 term_newln(p); 769 break; 770 default: 771 abort(); 772 } 773 } 774 775 static int 776 pre_RS(DECL_ARGS) 777 { 778 struct roffsu su; 779 780 switch (n->type) { 781 case ROFFT_BLOCK: 782 term_newln(p); 783 return 1; 784 case ROFFT_HEAD: 785 return 0; 786 case ROFFT_BODY: 787 break; 788 default: 789 abort(); 790 } 791 792 n = n->parent->head; 793 n->aux = SHRT_MAX + 1; 794 if (n->child == NULL) 795 n->aux = mt->lmargin[mt->lmargincur]; 796 else if (a2roffsu(n->child->string, &su, SCALE_EN) != NULL) 797 n->aux = term_hspan(p, &su); 798 if (n->aux < 0 && (size_t)(-n->aux) > mt->offset) 799 n->aux = -mt->offset; 800 else if (n->aux > SHRT_MAX) 801 n->aux = term_len(p, p->defindent); 802 803 mt->offset += n->aux; 804 p->tcol->offset = mt->offset; 805 p->tcol->rmargin = p->maxrmargin; 806 807 if (++mt->lmarginsz < MAXMARGINS) 808 mt->lmargincur = mt->lmarginsz; 809 810 mt->lmargin[mt->lmargincur] = term_len(p, 7); 811 return 1; 812 } 813 814 static void 815 post_RS(DECL_ARGS) 816 { 817 switch (n->type) { 818 case ROFFT_BLOCK: 819 case ROFFT_HEAD: 820 return; 821 case ROFFT_BODY: 822 break; 823 default: 824 abort(); 825 } 826 term_newln(p); 827 mt->offset -= n->parent->head->aux; 828 p->tcol->offset = mt->offset; 829 if (--mt->lmarginsz < MAXMARGINS) 830 mt->lmargincur = mt->lmarginsz; 831 } 832 833 static int 834 pre_SY(DECL_ARGS) 835 { 836 const struct roff_node *nn; 837 int len; /* Indentation in basic units. */ 838 839 switch (n->type) { 840 case ROFFT_BLOCK: 841 if ((nn = roff_node_prev(n)) == NULL || nn->tok != MAN_SY) 842 print_bvspace(p, n, mt->pardist); 843 return 1; 844 case ROFFT_HEAD: 845 case ROFFT_BODY: 846 break; 847 default: 848 abort(); 849 } 850 851 nn = n->parent->head->child; 852 len = term_len(p, 1); 853 if (nn != NULL) 854 len += term_strlen(p, nn->string); 855 856 switch (n->type) { 857 case ROFFT_HEAD: 858 p->tcol->offset = mt->offset; 859 p->tcol->rmargin = mt->offset + len; 860 if (n->next->child == NULL || 861 (n->next->child->flags & NODE_NOFILL) == 0) 862 p->flags |= TERMP_NOBREAK; 863 term_fontrepl(p, TERMFONT_BOLD); 864 break; 865 case ROFFT_BODY: 866 mt->lmargin[mt->lmargincur] = len; 867 p->tcol->offset = mt->offset + len; 868 p->tcol->rmargin = p->maxrmargin; 869 p->flags |= TERMP_NOSPACE; 870 break; 871 default: 872 abort(); 873 } 874 return 1; 875 } 876 877 static void 878 post_SY(DECL_ARGS) 879 { 880 switch (n->type) { 881 case ROFFT_BLOCK: 882 break; 883 case ROFFT_HEAD: 884 term_flushln(p); 885 p->flags &= ~TERMP_NOBREAK; 886 break; 887 case ROFFT_BODY: 888 term_newln(p); 889 p->tcol->offset = mt->offset; 890 break; 891 default: 892 abort(); 893 } 894 } 895 896 static int 897 pre_UR(DECL_ARGS) 898 { 899 return n->type != ROFFT_HEAD; 900 } 901 902 static void 903 post_UR(DECL_ARGS) 904 { 905 if (n->type != ROFFT_BLOCK) 906 return; 907 908 term_word(p, "<"); 909 p->flags |= TERMP_NOSPACE; 910 911 if (n->child->child != NULL) 912 print_man_node(p, mt, n->child->child, meta); 913 914 p->flags |= TERMP_NOSPACE; 915 term_word(p, ">"); 916 } 917 918 static void 919 print_man_node(DECL_ARGS) 920 { 921 const struct man_term_act *act; 922 int c; 923 924 /* 925 * In no-fill mode, break the output line at the beginning 926 * of new input lines except after \c, and nowhere else. 927 */ 928 929 if (n->flags & NODE_NOFILL) { 930 if (n->flags & NODE_LINE && 931 (p->flags & TERMP_NONEWLINE) == 0) 932 term_newln(p); 933 p->flags |= TERMP_BRNEVER; 934 } else { 935 if (n->flags & NODE_LINE) 936 term_tab_ref(p); 937 p->flags &= ~TERMP_BRNEVER; 938 } 939 940 if (n->flags & NODE_ID) 941 term_tag_write(n, p->line); 942 943 switch (n->type) { 944 case ROFFT_TEXT: 945 /* 946 * If we have a blank line, output a vertical space. 947 * If we have a space as the first character, break 948 * before printing the line's data. 949 */ 950 if (*n->string == '\0') { 951 if (p->flags & TERMP_NONEWLINE) 952 term_newln(p); 953 else 954 term_vspace(p); 955 return; 956 } else if (*n->string == ' ' && n->flags & NODE_LINE && 957 (p->flags & TERMP_NONEWLINE) == 0) 958 term_newln(p); 959 else if (n->flags & NODE_DELIMC) 960 p->flags |= TERMP_NOSPACE; 961 962 term_word(p, n->string); 963 goto out; 964 case ROFFT_COMMENT: 965 return; 966 case ROFFT_EQN: 967 if ( ! (n->flags & NODE_LINE)) 968 p->flags |= TERMP_NOSPACE; 969 term_eqn(p, n->eqn); 970 if (n->next != NULL && ! (n->next->flags & NODE_LINE)) 971 p->flags |= TERMP_NOSPACE; 972 return; 973 case ROFFT_TBL: 974 if (p->tbl.cols == NULL) 975 term_newln(p); 976 term_tbl(p, n->span); 977 return; 978 default: 979 break; 980 } 981 982 if (n->tok < ROFF_MAX) { 983 roff_term_pre(p, n); 984 return; 985 } 986 987 act = man_term_act(n->tok); 988 if ((act->flags & MAN_NOTEXT) == 0 && n->tok != MAN_SM) 989 term_fontrepl(p, TERMFONT_NONE); 990 991 c = 1; 992 if (act->pre != NULL) 993 c = (*act->pre)(p, mt, n, meta); 994 995 if (c && n->child != NULL) 996 print_man_nodelist(p, mt, n->child, meta); 997 998 if (act->post != NULL) 999 (*act->post)(p, mt, n, meta); 1000 if ((act->flags & MAN_NOTEXT) == 0 && n->tok != MAN_SM) 1001 term_fontrepl(p, TERMFONT_NONE); 1002 1003 out: 1004 if (n->parent->tok == MAN_HP && n->parent->type == ROFFT_BODY && 1005 n->prev == NULL && n->flags & NODE_NOFILL) { 1006 term_newln(p); 1007 p->tcol->offset = p->tcol->rmargin; 1008 p->tcol->rmargin = p->maxrmargin; 1009 } 1010 if (n->flags & NODE_EOS) 1011 p->flags |= TERMP_SENTENCE; 1012 } 1013 1014 static void 1015 print_man_nodelist(DECL_ARGS) 1016 { 1017 while (n != NULL) { 1018 print_man_node(p, mt, n, meta); 1019 n = n->next; 1020 } 1021 } 1022 1023 static void 1024 print_man_foot(struct termp *p, const struct roff_meta *meta) 1025 { 1026 char *title; 1027 size_t datelen, titlen; /* In basic units. */ 1028 1029 assert(meta->title != NULL); 1030 assert(meta->msec != NULL); 1031 1032 term_fontrepl(p, TERMFONT_NONE); 1033 if (meta->hasbody) 1034 term_vspace(p); 1035 1036 datelen = term_strlen(p, meta->date); 1037 mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec); 1038 titlen = term_strlen(p, title); 1039 1040 /* Bottom left corner: operating system. */ 1041 1042 p->tcol->offset = 0; 1043 p->tcol->rmargin = p->maxrmargin > datelen ? 1044 (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0; 1045 p->trailspace = 1; 1046 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; 1047 1048 if (meta->os) 1049 term_word(p, meta->os); 1050 term_flushln(p); 1051 1052 /* At the bottom in the middle: manual date. */ 1053 1054 p->tcol->offset = p->tcol->rmargin; 1055 p->tcol->rmargin = p->maxrmargin > titlen ? 1056 p->maxrmargin - titlen : 0; 1057 p->flags |= TERMP_NOSPACE; 1058 1059 term_word(p, meta->date); 1060 term_flushln(p); 1061 1062 /* Bottom right corner: manual title and section. */ 1063 1064 p->tcol->offset = p->tcol->rmargin; 1065 p->tcol->rmargin = p->maxrmargin; 1066 p->trailspace = 0; 1067 p->flags &= ~TERMP_NOBREAK; 1068 p->flags |= TERMP_NOSPACE; 1069 1070 term_word(p, title); 1071 term_flushln(p); 1072 1073 /* 1074 * Reset the terminal state for more output after the footer: 1075 * Some output modes, in particular PostScript and PDF, print 1076 * the header and the footer into a buffer such that it can be 1077 * reused for multiple output pages, then go on to format the 1078 * main text. 1079 */ 1080 1081 p->tcol->offset = 0; 1082 p->flags = 0; 1083 free(title); 1084 } 1085 1086 static void 1087 print_man_head(struct termp *p, const struct roff_meta *meta) 1088 { 1089 const char *volume; 1090 char *title; 1091 size_t vollen, titlen; /* In basic units. */ 1092 1093 assert(meta->title); 1094 assert(meta->msec); 1095 1096 volume = NULL == meta->vol ? "" : meta->vol; 1097 vollen = term_strlen(p, volume); 1098 1099 /* Top left corner: manual title and section. */ 1100 1101 mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec); 1102 titlen = term_strlen(p, title); 1103 1104 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; 1105 p->trailspace = 1; 1106 p->tcol->offset = 0; 1107 p->tcol->rmargin = 1108 titlen * 2 + term_len(p, 2) + vollen < p->maxrmargin ? 1109 (p->maxrmargin - vollen + term_len(p, 1)) / 2 : 1110 vollen < p->maxrmargin ? p->maxrmargin - vollen : 0; 1111 1112 term_word(p, title); 1113 term_flushln(p); 1114 1115 /* At the top in the middle: manual volume. */ 1116 1117 p->flags |= TERMP_NOSPACE; 1118 p->tcol->offset = p->tcol->rmargin; 1119 p->tcol->rmargin = p->tcol->offset + vollen + titlen < 1120 p->maxrmargin ? p->maxrmargin - titlen : p->maxrmargin; 1121 1122 term_word(p, volume); 1123 term_flushln(p); 1124 1125 /* Top right corner: title and section, again. */ 1126 1127 p->flags &= ~TERMP_NOBREAK; 1128 p->trailspace = 0; 1129 if (p->tcol->rmargin + titlen <= p->maxrmargin) { 1130 p->flags |= TERMP_NOSPACE; 1131 p->tcol->offset = p->tcol->rmargin; 1132 p->tcol->rmargin = p->maxrmargin; 1133 term_word(p, title); 1134 term_flushln(p); 1135 } 1136 1137 p->flags &= ~TERMP_NOSPACE; 1138 p->tcol->offset = 0; 1139 p->tcol->rmargin = p->maxrmargin; 1140 term_vspace(p); 1141 free(title); 1142 } 1143