1 /* $Id: mdoc_term.c,v 1.230 2011/05/17 14:38:34 kristaps Exp $ */ 2 /* 3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org> 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 AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 #ifdef HAVE_CONFIG_H 19 #include "config.h" 20 #endif 21 22 #include <sys/types.h> 23 24 #include <assert.h> 25 #include <ctype.h> 26 #include <stdint.h> 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <string.h> 30 31 #include "mandoc.h" 32 #include "out.h" 33 #include "term.h" 34 #include "mdoc.h" 35 #include "main.h" 36 37 #define INDENT 5 38 #define HALFINDENT 3 39 40 struct termpair { 41 struct termpair *ppair; 42 int count; 43 }; 44 45 #define DECL_ARGS struct termp *p, \ 46 struct termpair *pair, \ 47 const struct mdoc_meta *m, \ 48 const struct mdoc_node *n 49 50 struct termact { 51 int (*pre)(DECL_ARGS); 52 void (*post)(DECL_ARGS); 53 }; 54 55 static size_t a2width(const struct termp *, const char *); 56 static size_t a2height(const struct termp *, const char *); 57 static size_t a2offs(const struct termp *, const char *); 58 59 static void print_bvspace(struct termp *, 60 const struct mdoc_node *, 61 const struct mdoc_node *); 62 static void print_mdoc_node(DECL_ARGS); 63 static void print_mdoc_nodelist(DECL_ARGS); 64 static void print_mdoc_head(struct termp *, const void *); 65 static void print_mdoc_foot(struct termp *, const void *); 66 static void synopsis_pre(struct termp *, 67 const struct mdoc_node *); 68 69 static void termp____post(DECL_ARGS); 70 static void termp__t_post(DECL_ARGS); 71 static void termp_an_post(DECL_ARGS); 72 static void termp_bd_post(DECL_ARGS); 73 static void termp_bk_post(DECL_ARGS); 74 static void termp_bl_post(DECL_ARGS); 75 static void termp_d1_post(DECL_ARGS); 76 static void termp_fo_post(DECL_ARGS); 77 static void termp_in_post(DECL_ARGS); 78 static void termp_it_post(DECL_ARGS); 79 static void termp_lb_post(DECL_ARGS); 80 static void termp_nm_post(DECL_ARGS); 81 static void termp_pf_post(DECL_ARGS); 82 static void termp_quote_post(DECL_ARGS); 83 static void termp_sh_post(DECL_ARGS); 84 static void termp_ss_post(DECL_ARGS); 85 86 static int termp__a_pre(DECL_ARGS); 87 static int termp__t_pre(DECL_ARGS); 88 static int termp_an_pre(DECL_ARGS); 89 static int termp_ap_pre(DECL_ARGS); 90 static int termp_bd_pre(DECL_ARGS); 91 static int termp_bf_pre(DECL_ARGS); 92 static int termp_bk_pre(DECL_ARGS); 93 static int termp_bl_pre(DECL_ARGS); 94 static int termp_bold_pre(DECL_ARGS); 95 static int termp_bt_pre(DECL_ARGS); 96 static int termp_bx_pre(DECL_ARGS); 97 static int termp_cd_pre(DECL_ARGS); 98 static int termp_d1_pre(DECL_ARGS); 99 static int termp_ex_pre(DECL_ARGS); 100 static int termp_fa_pre(DECL_ARGS); 101 static int termp_fd_pre(DECL_ARGS); 102 static int termp_fl_pre(DECL_ARGS); 103 static int termp_fn_pre(DECL_ARGS); 104 static int termp_fo_pre(DECL_ARGS); 105 static int termp_ft_pre(DECL_ARGS); 106 static int termp_igndelim_pre(DECL_ARGS); 107 static int termp_in_pre(DECL_ARGS); 108 static int termp_it_pre(DECL_ARGS); 109 static int termp_li_pre(DECL_ARGS); 110 static int termp_lk_pre(DECL_ARGS); 111 static int termp_nd_pre(DECL_ARGS); 112 static int termp_nm_pre(DECL_ARGS); 113 static int termp_ns_pre(DECL_ARGS); 114 static int termp_quote_pre(DECL_ARGS); 115 static int termp_rs_pre(DECL_ARGS); 116 static int termp_rv_pre(DECL_ARGS); 117 static int termp_sh_pre(DECL_ARGS); 118 static int termp_sm_pre(DECL_ARGS); 119 static int termp_sp_pre(DECL_ARGS); 120 static int termp_ss_pre(DECL_ARGS); 121 static int termp_under_pre(DECL_ARGS); 122 static int termp_ud_pre(DECL_ARGS); 123 static int termp_vt_pre(DECL_ARGS); 124 static int termp_xr_pre(DECL_ARGS); 125 static int termp_xx_pre(DECL_ARGS); 126 127 static const struct termact termacts[MDOC_MAX] = { 128 { termp_ap_pre, NULL }, /* Ap */ 129 { NULL, NULL }, /* Dd */ 130 { NULL, NULL }, /* Dt */ 131 { NULL, NULL }, /* Os */ 132 { termp_sh_pre, termp_sh_post }, /* Sh */ 133 { termp_ss_pre, termp_ss_post }, /* Ss */ 134 { termp_sp_pre, NULL }, /* Pp */ 135 { termp_d1_pre, termp_d1_post }, /* D1 */ 136 { termp_d1_pre, termp_d1_post }, /* Dl */ 137 { termp_bd_pre, termp_bd_post }, /* Bd */ 138 { NULL, NULL }, /* Ed */ 139 { termp_bl_pre, termp_bl_post }, /* Bl */ 140 { NULL, NULL }, /* El */ 141 { termp_it_pre, termp_it_post }, /* It */ 142 { termp_under_pre, NULL }, /* Ad */ 143 { termp_an_pre, termp_an_post }, /* An */ 144 { termp_under_pre, NULL }, /* Ar */ 145 { termp_cd_pre, NULL }, /* Cd */ 146 { termp_bold_pre, NULL }, /* Cm */ 147 { NULL, NULL }, /* Dv */ 148 { NULL, NULL }, /* Er */ 149 { NULL, NULL }, /* Ev */ 150 { termp_ex_pre, NULL }, /* Ex */ 151 { termp_fa_pre, NULL }, /* Fa */ 152 { termp_fd_pre, NULL }, /* Fd */ 153 { termp_fl_pre, NULL }, /* Fl */ 154 { termp_fn_pre, NULL }, /* Fn */ 155 { termp_ft_pre, NULL }, /* Ft */ 156 { termp_bold_pre, NULL }, /* Ic */ 157 { termp_in_pre, termp_in_post }, /* In */ 158 { termp_li_pre, NULL }, /* Li */ 159 { termp_nd_pre, NULL }, /* Nd */ 160 { termp_nm_pre, termp_nm_post }, /* Nm */ 161 { termp_quote_pre, termp_quote_post }, /* Op */ 162 { NULL, NULL }, /* Ot */ 163 { termp_under_pre, NULL }, /* Pa */ 164 { termp_rv_pre, NULL }, /* Rv */ 165 { NULL, NULL }, /* St */ 166 { termp_under_pre, NULL }, /* Va */ 167 { termp_vt_pre, NULL }, /* Vt */ 168 { termp_xr_pre, NULL }, /* Xr */ 169 { termp__a_pre, termp____post }, /* %A */ 170 { termp_under_pre, termp____post }, /* %B */ 171 { NULL, termp____post }, /* %D */ 172 { termp_under_pre, termp____post }, /* %I */ 173 { termp_under_pre, termp____post }, /* %J */ 174 { NULL, termp____post }, /* %N */ 175 { NULL, termp____post }, /* %O */ 176 { NULL, termp____post }, /* %P */ 177 { NULL, termp____post }, /* %R */ 178 { termp__t_pre, termp__t_post }, /* %T */ 179 { NULL, termp____post }, /* %V */ 180 { NULL, NULL }, /* Ac */ 181 { termp_quote_pre, termp_quote_post }, /* Ao */ 182 { termp_quote_pre, termp_quote_post }, /* Aq */ 183 { NULL, NULL }, /* At */ 184 { NULL, NULL }, /* Bc */ 185 { termp_bf_pre, NULL }, /* Bf */ 186 { termp_quote_pre, termp_quote_post }, /* Bo */ 187 { termp_quote_pre, termp_quote_post }, /* Bq */ 188 { termp_xx_pre, NULL }, /* Bsx */ 189 { termp_bx_pre, NULL }, /* Bx */ 190 { NULL, NULL }, /* Db */ 191 { NULL, NULL }, /* Dc */ 192 { termp_quote_pre, termp_quote_post }, /* Do */ 193 { termp_quote_pre, termp_quote_post }, /* Dq */ 194 { NULL, NULL }, /* Ec */ /* FIXME: no space */ 195 { NULL, NULL }, /* Ef */ 196 { termp_under_pre, NULL }, /* Em */ 197 { NULL, NULL }, /* Eo */ 198 { termp_xx_pre, NULL }, /* Fx */ 199 { termp_bold_pre, NULL }, /* Ms */ 200 { termp_igndelim_pre, NULL }, /* No */ 201 { termp_ns_pre, NULL }, /* Ns */ 202 { termp_xx_pre, NULL }, /* Nx */ 203 { termp_xx_pre, NULL }, /* Ox */ 204 { NULL, NULL }, /* Pc */ 205 { termp_igndelim_pre, termp_pf_post }, /* Pf */ 206 { termp_quote_pre, termp_quote_post }, /* Po */ 207 { termp_quote_pre, termp_quote_post }, /* Pq */ 208 { NULL, NULL }, /* Qc */ 209 { termp_quote_pre, termp_quote_post }, /* Ql */ 210 { termp_quote_pre, termp_quote_post }, /* Qo */ 211 { termp_quote_pre, termp_quote_post }, /* Qq */ 212 { NULL, NULL }, /* Re */ 213 { termp_rs_pre, NULL }, /* Rs */ 214 { NULL, NULL }, /* Sc */ 215 { termp_quote_pre, termp_quote_post }, /* So */ 216 { termp_quote_pre, termp_quote_post }, /* Sq */ 217 { termp_sm_pre, NULL }, /* Sm */ 218 { termp_under_pre, NULL }, /* Sx */ 219 { termp_bold_pre, NULL }, /* Sy */ 220 { NULL, NULL }, /* Tn */ 221 { termp_xx_pre, NULL }, /* Ux */ 222 { NULL, NULL }, /* Xc */ 223 { NULL, NULL }, /* Xo */ 224 { termp_fo_pre, termp_fo_post }, /* Fo */ 225 { NULL, NULL }, /* Fc */ 226 { termp_quote_pre, termp_quote_post }, /* Oo */ 227 { NULL, NULL }, /* Oc */ 228 { termp_bk_pre, termp_bk_post }, /* Bk */ 229 { NULL, NULL }, /* Ek */ 230 { termp_bt_pre, NULL }, /* Bt */ 231 { NULL, NULL }, /* Hf */ 232 { NULL, NULL }, /* Fr */ 233 { termp_ud_pre, NULL }, /* Ud */ 234 { NULL, termp_lb_post }, /* Lb */ 235 { termp_sp_pre, NULL }, /* Lp */ 236 { termp_lk_pre, NULL }, /* Lk */ 237 { termp_under_pre, NULL }, /* Mt */ 238 { termp_quote_pre, termp_quote_post }, /* Brq */ 239 { termp_quote_pre, termp_quote_post }, /* Bro */ 240 { NULL, NULL }, /* Brc */ 241 { NULL, termp____post }, /* %C */ 242 { NULL, NULL }, /* Es */ /* TODO */ 243 { NULL, NULL }, /* En */ /* TODO */ 244 { termp_xx_pre, NULL }, /* Dx */ 245 { NULL, termp____post }, /* %Q */ 246 { termp_sp_pre, NULL }, /* br */ 247 { termp_sp_pre, NULL }, /* sp */ 248 { termp_under_pre, termp____post }, /* %U */ 249 { NULL, NULL }, /* Ta */ 250 }; 251 252 253 void 254 terminal_mdoc(void *arg, const struct mdoc *mdoc) 255 { 256 const struct mdoc_node *n; 257 const struct mdoc_meta *m; 258 struct termp *p; 259 260 p = (struct termp *)arg; 261 262 p->overstep = 0; 263 p->maxrmargin = p->defrmargin; 264 p->tabwidth = term_len(p, 5); 265 266 if (NULL == p->symtab) 267 p->symtab = mchars_alloc(); 268 269 n = mdoc_node(mdoc); 270 m = mdoc_meta(mdoc); 271 272 term_begin(p, print_mdoc_head, print_mdoc_foot, m); 273 274 if (n->child) 275 print_mdoc_nodelist(p, NULL, m, n->child); 276 277 term_end(p); 278 } 279 280 281 static void 282 print_mdoc_nodelist(DECL_ARGS) 283 { 284 285 print_mdoc_node(p, pair, m, n); 286 if (n->next) 287 print_mdoc_nodelist(p, pair, m, n->next); 288 } 289 290 291 /* ARGSUSED */ 292 static void 293 print_mdoc_node(DECL_ARGS) 294 { 295 int chld; 296 const void *font; 297 struct termpair npair; 298 size_t offset, rmargin; 299 300 chld = 1; 301 offset = p->offset; 302 rmargin = p->rmargin; 303 font = term_fontq(p); 304 305 memset(&npair, 0, sizeof(struct termpair)); 306 npair.ppair = pair; 307 308 /* 309 * Keeps only work until the end of a line. If a keep was 310 * invoked in a prior line, revert it to PREKEEP. 311 * 312 * Also let SYNPRETTY sections behave as if they were wrapped 313 * in a `Bk' block. 314 */ 315 316 if (TERMP_KEEP & p->flags || MDOC_SYNPRETTY & n->flags) { 317 if (n->prev && n->prev->line != n->line) { 318 p->flags &= ~TERMP_KEEP; 319 p->flags |= TERMP_PREKEEP; 320 } else if (NULL == n->prev) { 321 if (n->parent && n->parent->line != n->line) { 322 p->flags &= ~TERMP_KEEP; 323 p->flags |= TERMP_PREKEEP; 324 } 325 } 326 } 327 328 /* 329 * Since SYNPRETTY sections aren't "turned off" with `Ek', 330 * we have to intuit whether we should disable formatting. 331 */ 332 333 if ( ! (MDOC_SYNPRETTY & n->flags) && 334 ((n->prev && MDOC_SYNPRETTY & n->prev->flags) || 335 (n->parent && MDOC_SYNPRETTY & n->parent->flags))) 336 p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); 337 338 /* 339 * After the keep flags have been set up, we may now 340 * produce output. Note that some pre-handlers do so. 341 */ 342 343 switch (n->type) { 344 case (MDOC_TEXT): 345 if (' ' == *n->string && MDOC_LINE & n->flags) 346 term_newln(p); 347 if (MDOC_DELIMC & n->flags) 348 p->flags |= TERMP_NOSPACE; 349 term_word(p, n->string); 350 if (MDOC_DELIMO & n->flags) 351 p->flags |= TERMP_NOSPACE; 352 break; 353 case (MDOC_EQN): 354 term_word(p, n->eqn->data); 355 break; 356 case (MDOC_TBL): 357 term_tbl(p, n->span); 358 break; 359 default: 360 if (termacts[n->tok].pre && ENDBODY_NOT == n->end) 361 chld = (*termacts[n->tok].pre) 362 (p, &npair, m, n); 363 break; 364 } 365 366 if (chld && n->child) 367 print_mdoc_nodelist(p, &npair, m, n->child); 368 369 term_fontpopq(p, font); 370 371 switch (n->type) { 372 case (MDOC_TEXT): 373 break; 374 case (MDOC_TBL): 375 break; 376 case (MDOC_EQN): 377 break; 378 default: 379 if ( ! termacts[n->tok].post || MDOC_ENDED & n->flags) 380 break; 381 (void)(*termacts[n->tok].post)(p, &npair, m, n); 382 383 /* 384 * Explicit end tokens not only call the post 385 * handler, but also tell the respective block 386 * that it must not call the post handler again. 387 */ 388 if (ENDBODY_NOT != n->end) 389 n->pending->flags |= MDOC_ENDED; 390 391 /* 392 * End of line terminating an implicit block 393 * while an explicit block is still open. 394 * Continue the explicit block without spacing. 395 */ 396 if (ENDBODY_NOSPACE == n->end) 397 p->flags |= TERMP_NOSPACE; 398 break; 399 } 400 401 if (MDOC_EOS & n->flags) 402 p->flags |= TERMP_SENTENCE; 403 404 p->offset = offset; 405 p->rmargin = rmargin; 406 } 407 408 409 static void 410 print_mdoc_foot(struct termp *p, const void *arg) 411 { 412 const struct mdoc_meta *m; 413 414 m = (const struct mdoc_meta *)arg; 415 416 term_fontrepl(p, TERMFONT_NONE); 417 418 /* 419 * Output the footer in new-groff style, that is, three columns 420 * with the middle being the manual date and flanking columns 421 * being the operating system: 422 * 423 * SYSTEM DATE SYSTEM 424 */ 425 426 term_vspace(p); 427 428 p->offset = 0; 429 p->rmargin = (p->maxrmargin - 430 term_strlen(p, m->date) + term_len(p, 1)) / 2; 431 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; 432 433 term_word(p, m->os); 434 term_flushln(p); 435 436 p->offset = p->rmargin; 437 p->rmargin = p->maxrmargin - term_strlen(p, m->os); 438 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; 439 440 term_word(p, m->date); 441 term_flushln(p); 442 443 p->offset = p->rmargin; 444 p->rmargin = p->maxrmargin; 445 p->flags &= ~TERMP_NOBREAK; 446 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; 447 448 term_word(p, m->os); 449 term_flushln(p); 450 451 p->offset = 0; 452 p->rmargin = p->maxrmargin; 453 p->flags = 0; 454 } 455 456 457 static void 458 print_mdoc_head(struct termp *p, const void *arg) 459 { 460 char buf[BUFSIZ], title[BUFSIZ]; 461 const struct mdoc_meta *m; 462 463 m = (const struct mdoc_meta *)arg; 464 465 p->rmargin = p->maxrmargin; 466 p->offset = 0; 467 468 /* 469 * The header is strange. It has three components, which are 470 * really two with the first duplicated. It goes like this: 471 * 472 * IDENTIFIER TITLE IDENTIFIER 473 * 474 * The IDENTIFIER is NAME(SECTION), which is the command-name 475 * (if given, or "unknown" if not) followed by the manual page 476 * section. These are given in `Dt'. The TITLE is a free-form 477 * string depending on the manual volume. If not specified, it 478 * switches on the manual section. 479 */ 480 481 assert(m->vol); 482 strlcpy(buf, m->vol, BUFSIZ); 483 484 if (m->arch) { 485 strlcat(buf, " (", BUFSIZ); 486 strlcat(buf, m->arch, BUFSIZ); 487 strlcat(buf, ")", BUFSIZ); 488 } 489 490 snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec); 491 492 p->offset = 0; 493 p->rmargin = (p->maxrmargin - 494 term_strlen(p, buf) + term_len(p, 1)) / 2; 495 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; 496 497 term_word(p, title); 498 term_flushln(p); 499 500 p->offset = p->rmargin; 501 p->rmargin = p->maxrmargin - term_strlen(p, title); 502 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; 503 504 term_word(p, buf); 505 term_flushln(p); 506 507 p->offset = p->rmargin; 508 p->rmargin = p->maxrmargin; 509 p->flags &= ~TERMP_NOBREAK; 510 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; 511 512 term_word(p, title); 513 term_flushln(p); 514 515 p->offset = 0; 516 p->rmargin = p->maxrmargin; 517 p->flags &= ~TERMP_NOSPACE; 518 } 519 520 521 static size_t 522 a2height(const struct termp *p, const char *v) 523 { 524 struct roffsu su; 525 526 assert(v); 527 if ( ! a2roffsu(v, &su, SCALE_VS)) 528 SCALE_VS_INIT(&su, term_len(p, 1)); 529 530 return(term_vspan(p, &su)); 531 } 532 533 534 static size_t 535 a2width(const struct termp *p, const char *v) 536 { 537 struct roffsu su; 538 539 assert(v); 540 if ( ! a2roffsu(v, &su, SCALE_MAX)) 541 SCALE_HS_INIT(&su, term_strlen(p, v)); 542 543 return(term_hspan(p, &su)); 544 } 545 546 547 static size_t 548 a2offs(const struct termp *p, const char *v) 549 { 550 struct roffsu su; 551 552 if ('\0' == *v) 553 return(0); 554 else if (0 == strcmp(v, "left")) 555 return(0); 556 else if (0 == strcmp(v, "indent")) 557 return(term_len(p, INDENT + 1)); 558 else if (0 == strcmp(v, "indent-two")) 559 return(term_len(p, (INDENT + 1) * 2)); 560 else if ( ! a2roffsu(v, &su, SCALE_MAX)) 561 SCALE_HS_INIT(&su, term_strlen(p, v)); 562 563 return(term_hspan(p, &su)); 564 } 565 566 567 /* 568 * Determine how much space to print out before block elements of `It' 569 * (and thus `Bl') and `Bd'. And then go ahead and print that space, 570 * too. 571 */ 572 static void 573 print_bvspace(struct termp *p, 574 const struct mdoc_node *bl, 575 const struct mdoc_node *n) 576 { 577 const struct mdoc_node *nn; 578 579 term_newln(p); 580 581 if (MDOC_Bd == bl->tok && bl->norm->Bd.comp) 582 return; 583 if (MDOC_Bl == bl->tok && bl->norm->Bl.comp) 584 return; 585 586 /* Do not vspace directly after Ss/Sh. */ 587 588 for (nn = n; nn; nn = nn->parent) { 589 if (MDOC_BLOCK != nn->type) 590 continue; 591 if (MDOC_Ss == nn->tok) 592 return; 593 if (MDOC_Sh == nn->tok) 594 return; 595 if (NULL == nn->prev) 596 continue; 597 break; 598 } 599 600 /* A `-column' does not assert vspace within the list. */ 601 602 if (MDOC_Bl == bl->tok && LIST_column == bl->norm->Bl.type) 603 if (n->prev && MDOC_It == n->prev->tok) 604 return; 605 606 /* A `-diag' without body does not vspace. */ 607 608 if (MDOC_Bl == bl->tok && LIST_diag == bl->norm->Bl.type) 609 if (n->prev && MDOC_It == n->prev->tok) { 610 assert(n->prev->body); 611 if (NULL == n->prev->body->child) 612 return; 613 } 614 615 term_vspace(p); 616 } 617 618 619 /* ARGSUSED */ 620 static int 621 termp_it_pre(DECL_ARGS) 622 { 623 const struct mdoc_node *bl, *nn; 624 char buf[7]; 625 int i; 626 size_t width, offset, ncols, dcol; 627 enum mdoc_list type; 628 629 if (MDOC_BLOCK == n->type) { 630 print_bvspace(p, n->parent->parent, n); 631 return(1); 632 } 633 634 bl = n->parent->parent->parent; 635 type = bl->norm->Bl.type; 636 637 /* 638 * First calculate width and offset. This is pretty easy unless 639 * we're a -column list, in which case all prior columns must 640 * be accounted for. 641 */ 642 643 width = offset = 0; 644 645 if (bl->norm->Bl.offs) 646 offset = a2offs(p, bl->norm->Bl.offs); 647 648 switch (type) { 649 case (LIST_column): 650 if (MDOC_HEAD == n->type) 651 break; 652 653 /* 654 * Imitate groff's column handling: 655 * - For each earlier column, add its width. 656 * - For less than 5 columns, add four more blanks per 657 * column. 658 * - For exactly 5 columns, add three more blank per 659 * column. 660 * - For more than 5 columns, add only one column. 661 */ 662 ncols = bl->norm->Bl.ncols; 663 664 /* LINTED */ 665 dcol = ncols < 5 ? term_len(p, 4) : 666 ncols == 5 ? term_len(p, 3) : term_len(p, 1); 667 668 /* 669 * Calculate the offset by applying all prior MDOC_BODY, 670 * so we stop at the MDOC_HEAD (NULL == nn->prev). 671 */ 672 673 for (i = 0, nn = n->prev; 674 nn->prev && i < (int)ncols; 675 nn = nn->prev, i++) 676 offset += dcol + a2width 677 (p, bl->norm->Bl.cols[i]); 678 679 /* 680 * When exceeding the declared number of columns, leave 681 * the remaining widths at 0. This will later be 682 * adjusted to the default width of 10, or, for the last 683 * column, stretched to the right margin. 684 */ 685 if (i >= (int)ncols) 686 break; 687 688 /* 689 * Use the declared column widths, extended as explained 690 * in the preceding paragraph. 691 */ 692 width = a2width(p, bl->norm->Bl.cols[i]) + dcol; 693 break; 694 default: 695 if (NULL == bl->norm->Bl.width) 696 break; 697 698 /* 699 * Note: buffer the width by 2, which is groff's magic 700 * number for buffering single arguments. See the above 701 * handling for column for how this changes. 702 */ 703 assert(bl->norm->Bl.width); 704 width = a2width(p, bl->norm->Bl.width) + term_len(p, 2); 705 break; 706 } 707 708 /* 709 * List-type can override the width in the case of fixed-head 710 * values (bullet, dash/hyphen, enum). Tags need a non-zero 711 * offset. 712 */ 713 714 switch (type) { 715 case (LIST_bullet): 716 /* FALLTHROUGH */ 717 case (LIST_dash): 718 /* FALLTHROUGH */ 719 case (LIST_hyphen): 720 if (width < term_len(p, 4)) 721 width = term_len(p, 4); 722 break; 723 case (LIST_enum): 724 if (width < term_len(p, 5)) 725 width = term_len(p, 5); 726 break; 727 case (LIST_hang): 728 if (0 == width) 729 width = term_len(p, 8); 730 break; 731 case (LIST_column): 732 /* FALLTHROUGH */ 733 case (LIST_tag): 734 if (0 == width) 735 width = term_len(p, 10); 736 break; 737 default: 738 break; 739 } 740 741 /* 742 * Whitespace control. Inset bodies need an initial space, 743 * while diagonal bodies need two. 744 */ 745 746 p->flags |= TERMP_NOSPACE; 747 748 switch (type) { 749 case (LIST_diag): 750 if (MDOC_BODY == n->type) 751 term_word(p, "\\ \\ "); 752 break; 753 case (LIST_inset): 754 if (MDOC_BODY == n->type) 755 term_word(p, "\\ "); 756 break; 757 default: 758 break; 759 } 760 761 p->flags |= TERMP_NOSPACE; 762 763 switch (type) { 764 case (LIST_diag): 765 if (MDOC_HEAD == n->type) 766 term_fontpush(p, TERMFONT_BOLD); 767 break; 768 default: 769 break; 770 } 771 772 /* 773 * Pad and break control. This is the tricky part. These flags 774 * are documented in term_flushln() in term.c. Note that we're 775 * going to unset all of these flags in termp_it_post() when we 776 * exit. 777 */ 778 779 switch (type) { 780 case (LIST_bullet): 781 /* FALLTHROUGH */ 782 case (LIST_dash): 783 /* FALLTHROUGH */ 784 case (LIST_enum): 785 /* FALLTHROUGH */ 786 case (LIST_hyphen): 787 if (MDOC_HEAD == n->type) 788 p->flags |= TERMP_NOBREAK; 789 else 790 p->flags |= TERMP_NOLPAD; 791 break; 792 case (LIST_hang): 793 if (MDOC_HEAD == n->type) 794 p->flags |= TERMP_NOBREAK; 795 else 796 p->flags |= TERMP_NOLPAD; 797 798 if (MDOC_HEAD != n->type) 799 break; 800 801 /* 802 * This is ugly. If `-hang' is specified and the body 803 * is a `Bl' or `Bd', then we want basically to nullify 804 * the "overstep" effect in term_flushln() and treat 805 * this as a `-ohang' list instead. 806 */ 807 if (n->next->child && 808 (MDOC_Bl == n->next->child->tok || 809 MDOC_Bd == n->next->child->tok)) { 810 p->flags &= ~TERMP_NOBREAK; 811 p->flags &= ~TERMP_NOLPAD; 812 } else 813 p->flags |= TERMP_HANG; 814 break; 815 case (LIST_tag): 816 if (MDOC_HEAD == n->type) 817 p->flags |= TERMP_NOBREAK | TERMP_TWOSPACE; 818 else 819 p->flags |= TERMP_NOLPAD; 820 821 if (MDOC_HEAD != n->type) 822 break; 823 if (NULL == n->next || NULL == n->next->child) 824 p->flags |= TERMP_DANGLE; 825 break; 826 case (LIST_column): 827 if (MDOC_HEAD == n->type) 828 break; 829 830 if (NULL == n->next) 831 p->flags &= ~TERMP_NOBREAK; 832 else 833 p->flags |= TERMP_NOBREAK; 834 835 assert(n->prev); 836 if (MDOC_BODY == n->prev->type) 837 p->flags |= TERMP_NOLPAD; 838 839 break; 840 case (LIST_diag): 841 if (MDOC_HEAD == n->type) 842 p->flags |= TERMP_NOBREAK; 843 break; 844 default: 845 break; 846 } 847 848 /* 849 * Margin control. Set-head-width lists have their right 850 * margins shortened. The body for these lists has the offset 851 * necessarily lengthened. Everybody gets the offset. 852 */ 853 854 p->offset += offset; 855 856 switch (type) { 857 case (LIST_hang): 858 /* 859 * Same stipulation as above, regarding `-hang'. We 860 * don't want to recalculate rmargin and offsets when 861 * using `Bd' or `Bl' within `-hang' overstep lists. 862 */ 863 if (MDOC_HEAD == n->type && n->next->child && 864 (MDOC_Bl == n->next->child->tok || 865 MDOC_Bd == n->next->child->tok)) 866 break; 867 /* FALLTHROUGH */ 868 case (LIST_bullet): 869 /* FALLTHROUGH */ 870 case (LIST_dash): 871 /* FALLTHROUGH */ 872 case (LIST_enum): 873 /* FALLTHROUGH */ 874 case (LIST_hyphen): 875 /* FALLTHROUGH */ 876 case (LIST_tag): 877 assert(width); 878 if (MDOC_HEAD == n->type) 879 p->rmargin = p->offset + width; 880 else 881 p->offset += width; 882 break; 883 case (LIST_column): 884 assert(width); 885 p->rmargin = p->offset + width; 886 /* 887 * XXX - this behaviour is not documented: the 888 * right-most column is filled to the right margin. 889 */ 890 if (MDOC_HEAD == n->type) 891 break; 892 if (NULL == n->next && p->rmargin < p->maxrmargin) 893 p->rmargin = p->maxrmargin; 894 break; 895 default: 896 break; 897 } 898 899 /* 900 * The dash, hyphen, bullet and enum lists all have a special 901 * HEAD character (temporarily bold, in some cases). 902 */ 903 904 if (MDOC_HEAD == n->type) 905 switch (type) { 906 case (LIST_bullet): 907 term_fontpush(p, TERMFONT_BOLD); 908 term_word(p, "\\[bu]"); 909 term_fontpop(p); 910 break; 911 case (LIST_dash): 912 /* FALLTHROUGH */ 913 case (LIST_hyphen): 914 term_fontpush(p, TERMFONT_BOLD); 915 term_word(p, "\\(hy"); 916 term_fontpop(p); 917 break; 918 case (LIST_enum): 919 (pair->ppair->ppair->count)++; 920 snprintf(buf, sizeof(buf), "%d.", 921 pair->ppair->ppair->count); 922 term_word(p, buf); 923 break; 924 default: 925 break; 926 } 927 928 /* 929 * If we're not going to process our children, indicate so here. 930 */ 931 932 switch (type) { 933 case (LIST_bullet): 934 /* FALLTHROUGH */ 935 case (LIST_item): 936 /* FALLTHROUGH */ 937 case (LIST_dash): 938 /* FALLTHROUGH */ 939 case (LIST_hyphen): 940 /* FALLTHROUGH */ 941 case (LIST_enum): 942 if (MDOC_HEAD == n->type) 943 return(0); 944 break; 945 case (LIST_column): 946 if (MDOC_HEAD == n->type) 947 return(0); 948 break; 949 default: 950 break; 951 } 952 953 return(1); 954 } 955 956 957 /* ARGSUSED */ 958 static void 959 termp_it_post(DECL_ARGS) 960 { 961 enum mdoc_list type; 962 963 if (MDOC_BLOCK == n->type) 964 return; 965 966 type = n->parent->parent->parent->norm->Bl.type; 967 968 switch (type) { 969 case (LIST_item): 970 /* FALLTHROUGH */ 971 case (LIST_diag): 972 /* FALLTHROUGH */ 973 case (LIST_inset): 974 if (MDOC_BODY == n->type) 975 term_newln(p); 976 break; 977 case (LIST_column): 978 if (MDOC_BODY == n->type) 979 term_flushln(p); 980 break; 981 default: 982 term_newln(p); 983 break; 984 } 985 986 /* 987 * Now that our output is flushed, we can reset our tags. Since 988 * only `It' sets these flags, we're free to assume that nobody 989 * has munged them in the meanwhile. 990 */ 991 992 p->flags &= ~TERMP_DANGLE; 993 p->flags &= ~TERMP_NOBREAK; 994 p->flags &= ~TERMP_TWOSPACE; 995 p->flags &= ~TERMP_NOLPAD; 996 p->flags &= ~TERMP_HANG; 997 } 998 999 1000 /* ARGSUSED */ 1001 static int 1002 termp_nm_pre(DECL_ARGS) 1003 { 1004 1005 if (MDOC_BLOCK == n->type) 1006 return(1); 1007 1008 if (MDOC_BODY == n->type) { 1009 if (NULL == n->child) 1010 return(0); 1011 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; 1012 p->offset += term_len(p, 1) + 1013 (NULL == n->prev->child ? term_strlen(p, m->name) : 1014 MDOC_TEXT == n->prev->child->type ? 1015 term_strlen(p, n->prev->child->string) : 1016 term_len(p, 5)); 1017 return(1); 1018 } 1019 1020 if (NULL == n->child && NULL == m->name) 1021 return(0); 1022 1023 if (MDOC_HEAD == n->type) 1024 synopsis_pre(p, n->parent); 1025 1026 if (MDOC_HEAD == n->type && n->next->child) { 1027 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; 1028 p->rmargin = p->offset + term_len(p, 1); 1029 if (NULL == n->child) { 1030 p->rmargin += term_strlen(p, m->name); 1031 } else if (MDOC_TEXT == n->child->type) { 1032 p->rmargin += term_strlen(p, n->child->string); 1033 if (n->child->next) 1034 p->flags |= TERMP_HANG; 1035 } else { 1036 p->rmargin += term_len(p, 5); 1037 p->flags |= TERMP_HANG; 1038 } 1039 } 1040 1041 term_fontpush(p, TERMFONT_BOLD); 1042 if (NULL == n->child) 1043 term_word(p, m->name); 1044 return(1); 1045 } 1046 1047 1048 /* ARGSUSED */ 1049 static void 1050 termp_nm_post(DECL_ARGS) 1051 { 1052 1053 if (MDOC_HEAD == n->type && n->next->child) { 1054 term_flushln(p); 1055 p->flags &= ~(TERMP_NOBREAK | TERMP_HANG); 1056 } else if (MDOC_BODY == n->type && n->child) { 1057 term_flushln(p); 1058 p->flags &= ~TERMP_NOLPAD; 1059 } 1060 } 1061 1062 1063 /* ARGSUSED */ 1064 static int 1065 termp_fl_pre(DECL_ARGS) 1066 { 1067 1068 term_fontpush(p, TERMFONT_BOLD); 1069 term_word(p, "\\-"); 1070 1071 if (n->child) 1072 p->flags |= TERMP_NOSPACE; 1073 else if (n->next && n->next->line == n->line) 1074 p->flags |= TERMP_NOSPACE; 1075 1076 return(1); 1077 } 1078 1079 1080 /* ARGSUSED */ 1081 static int 1082 termp__a_pre(DECL_ARGS) 1083 { 1084 1085 if (n->prev && MDOC__A == n->prev->tok) 1086 if (NULL == n->next || MDOC__A != n->next->tok) 1087 term_word(p, "and"); 1088 1089 return(1); 1090 } 1091 1092 1093 /* ARGSUSED */ 1094 static int 1095 termp_an_pre(DECL_ARGS) 1096 { 1097 1098 if (NULL == n->child) 1099 return(1); 1100 1101 /* 1102 * If not in the AUTHORS section, `An -split' will cause 1103 * newlines to occur before the author name. If in the AUTHORS 1104 * section, by default, the first `An' invocation is nosplit, 1105 * then all subsequent ones, regardless of whether interspersed 1106 * with other macros/text, are split. -split, in this case, 1107 * will override the condition of the implied first -nosplit. 1108 */ 1109 1110 if (n->sec == SEC_AUTHORS) { 1111 if ( ! (TERMP_ANPREC & p->flags)) { 1112 if (TERMP_SPLIT & p->flags) 1113 term_newln(p); 1114 return(1); 1115 } 1116 if (TERMP_NOSPLIT & p->flags) 1117 return(1); 1118 term_newln(p); 1119 return(1); 1120 } 1121 1122 if (TERMP_SPLIT & p->flags) 1123 term_newln(p); 1124 1125 return(1); 1126 } 1127 1128 1129 /* ARGSUSED */ 1130 static void 1131 termp_an_post(DECL_ARGS) 1132 { 1133 1134 if (n->child) { 1135 if (SEC_AUTHORS == n->sec) 1136 p->flags |= TERMP_ANPREC; 1137 return; 1138 } 1139 1140 if (AUTH_split == n->norm->An.auth) { 1141 p->flags &= ~TERMP_NOSPLIT; 1142 p->flags |= TERMP_SPLIT; 1143 } else if (AUTH_nosplit == n->norm->An.auth) { 1144 p->flags &= ~TERMP_SPLIT; 1145 p->flags |= TERMP_NOSPLIT; 1146 } 1147 1148 } 1149 1150 1151 /* ARGSUSED */ 1152 static int 1153 termp_ns_pre(DECL_ARGS) 1154 { 1155 1156 if ( ! (MDOC_LINE & n->flags)) 1157 p->flags |= TERMP_NOSPACE; 1158 return(1); 1159 } 1160 1161 1162 /* ARGSUSED */ 1163 static int 1164 termp_rs_pre(DECL_ARGS) 1165 { 1166 1167 if (SEC_SEE_ALSO != n->sec) 1168 return(1); 1169 if (MDOC_BLOCK == n->type && n->prev) 1170 term_vspace(p); 1171 return(1); 1172 } 1173 1174 1175 /* ARGSUSED */ 1176 static int 1177 termp_rv_pre(DECL_ARGS) 1178 { 1179 int nchild; 1180 1181 term_newln(p); 1182 term_word(p, "The"); 1183 1184 nchild = n->nchild; 1185 for (n = n->child; n; n = n->next) { 1186 term_fontpush(p, TERMFONT_BOLD); 1187 term_word(p, n->string); 1188 term_fontpop(p); 1189 1190 p->flags |= TERMP_NOSPACE; 1191 term_word(p, "()"); 1192 1193 if (nchild > 2 && n->next) { 1194 p->flags |= TERMP_NOSPACE; 1195 term_word(p, ","); 1196 } 1197 1198 if (n->next && NULL == n->next->next) 1199 term_word(p, "and"); 1200 } 1201 1202 if (nchild > 1) 1203 term_word(p, "functions return"); 1204 else 1205 term_word(p, "function returns"); 1206 1207 term_word(p, "the value 0 if successful; otherwise the value " 1208 "-1 is returned and the global variable"); 1209 1210 term_fontpush(p, TERMFONT_UNDER); 1211 term_word(p, "errno"); 1212 term_fontpop(p); 1213 1214 term_word(p, "is set to indicate the error."); 1215 p->flags |= TERMP_SENTENCE; 1216 1217 return(0); 1218 } 1219 1220 1221 /* ARGSUSED */ 1222 static int 1223 termp_ex_pre(DECL_ARGS) 1224 { 1225 int nchild; 1226 1227 term_newln(p); 1228 term_word(p, "The"); 1229 1230 nchild = n->nchild; 1231 for (n = n->child; n; n = n->next) { 1232 term_fontpush(p, TERMFONT_BOLD); 1233 term_word(p, n->string); 1234 term_fontpop(p); 1235 1236 if (nchild > 2 && n->next) { 1237 p->flags |= TERMP_NOSPACE; 1238 term_word(p, ","); 1239 } 1240 1241 if (n->next && NULL == n->next->next) 1242 term_word(p, "and"); 1243 } 1244 1245 if (nchild > 1) 1246 term_word(p, "utilities exit"); 1247 else 1248 term_word(p, "utility exits"); 1249 1250 term_word(p, "0 on success, and >0 if an error occurs."); 1251 1252 p->flags |= TERMP_SENTENCE; 1253 return(0); 1254 } 1255 1256 1257 /* ARGSUSED */ 1258 static int 1259 termp_nd_pre(DECL_ARGS) 1260 { 1261 1262 if (MDOC_BODY != n->type) 1263 return(1); 1264 1265 #if defined(__OpenBSD__) || defined(__linux__) 1266 term_word(p, "\\(en"); 1267 #else 1268 term_word(p, "\\(em"); 1269 #endif 1270 return(1); 1271 } 1272 1273 1274 /* ARGSUSED */ 1275 static int 1276 termp_bl_pre(DECL_ARGS) 1277 { 1278 1279 return(MDOC_HEAD != n->type); 1280 } 1281 1282 1283 /* ARGSUSED */ 1284 static void 1285 termp_bl_post(DECL_ARGS) 1286 { 1287 1288 if (MDOC_BLOCK == n->type) 1289 term_newln(p); 1290 } 1291 1292 /* ARGSUSED */ 1293 static int 1294 termp_xr_pre(DECL_ARGS) 1295 { 1296 1297 if (NULL == (n = n->child)) 1298 return(0); 1299 1300 assert(MDOC_TEXT == n->type); 1301 term_word(p, n->string); 1302 1303 if (NULL == (n = n->next)) 1304 return(0); 1305 1306 p->flags |= TERMP_NOSPACE; 1307 term_word(p, "("); 1308 p->flags |= TERMP_NOSPACE; 1309 1310 assert(MDOC_TEXT == n->type); 1311 term_word(p, n->string); 1312 1313 p->flags |= TERMP_NOSPACE; 1314 term_word(p, ")"); 1315 1316 return(0); 1317 } 1318 1319 /* 1320 * This decides how to assert whitespace before any of the SYNOPSIS set 1321 * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain 1322 * macro combos). 1323 */ 1324 static void 1325 synopsis_pre(struct termp *p, const struct mdoc_node *n) 1326 { 1327 /* 1328 * Obviously, if we're not in a SYNOPSIS or no prior macros 1329 * exist, do nothing. 1330 */ 1331 if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags)) 1332 return; 1333 1334 /* 1335 * If we're the second in a pair of like elements, emit our 1336 * newline and return. UNLESS we're `Fo', `Fn', `Fn', in which 1337 * case we soldier on. 1338 */ 1339 if (n->prev->tok == n->tok && 1340 MDOC_Ft != n->tok && 1341 MDOC_Fo != n->tok && 1342 MDOC_Fn != n->tok) { 1343 term_newln(p); 1344 return; 1345 } 1346 1347 /* 1348 * If we're one of the SYNOPSIS set and non-like pair-wise after 1349 * another (or Fn/Fo, which we've let slip through) then assert 1350 * vertical space, else only newline and move on. 1351 */ 1352 switch (n->prev->tok) { 1353 case (MDOC_Fd): 1354 /* FALLTHROUGH */ 1355 case (MDOC_Fn): 1356 /* FALLTHROUGH */ 1357 case (MDOC_Fo): 1358 /* FALLTHROUGH */ 1359 case (MDOC_In): 1360 /* FALLTHROUGH */ 1361 case (MDOC_Vt): 1362 term_vspace(p); 1363 break; 1364 case (MDOC_Ft): 1365 if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) { 1366 term_vspace(p); 1367 break; 1368 } 1369 /* FALLTHROUGH */ 1370 default: 1371 term_newln(p); 1372 break; 1373 } 1374 } 1375 1376 1377 static int 1378 termp_vt_pre(DECL_ARGS) 1379 { 1380 1381 if (MDOC_ELEM == n->type) { 1382 synopsis_pre(p, n); 1383 return(termp_under_pre(p, pair, m, n)); 1384 } else if (MDOC_BLOCK == n->type) { 1385 synopsis_pre(p, n); 1386 return(1); 1387 } else if (MDOC_HEAD == n->type) 1388 return(0); 1389 1390 return(termp_under_pre(p, pair, m, n)); 1391 } 1392 1393 1394 /* ARGSUSED */ 1395 static int 1396 termp_bold_pre(DECL_ARGS) 1397 { 1398 1399 term_fontpush(p, TERMFONT_BOLD); 1400 return(1); 1401 } 1402 1403 1404 /* ARGSUSED */ 1405 static int 1406 termp_fd_pre(DECL_ARGS) 1407 { 1408 1409 synopsis_pre(p, n); 1410 return(termp_bold_pre(p, pair, m, n)); 1411 } 1412 1413 1414 /* ARGSUSED */ 1415 static int 1416 termp_sh_pre(DECL_ARGS) 1417 { 1418 1419 /* No vspace between consecutive `Sh' calls. */ 1420 1421 switch (n->type) { 1422 case (MDOC_BLOCK): 1423 if (n->prev && MDOC_Sh == n->prev->tok) 1424 if (NULL == n->prev->body->child) 1425 break; 1426 term_vspace(p); 1427 break; 1428 case (MDOC_HEAD): 1429 term_fontpush(p, TERMFONT_BOLD); 1430 break; 1431 case (MDOC_BODY): 1432 p->offset = term_len(p, INDENT); 1433 break; 1434 default: 1435 break; 1436 } 1437 return(1); 1438 } 1439 1440 1441 /* ARGSUSED */ 1442 static void 1443 termp_sh_post(DECL_ARGS) 1444 { 1445 1446 switch (n->type) { 1447 case (MDOC_HEAD): 1448 term_newln(p); 1449 break; 1450 case (MDOC_BODY): 1451 term_newln(p); 1452 p->offset = 0; 1453 break; 1454 default: 1455 break; 1456 } 1457 } 1458 1459 1460 /* ARGSUSED */ 1461 static int 1462 termp_bt_pre(DECL_ARGS) 1463 { 1464 1465 term_word(p, "is currently in beta test."); 1466 p->flags |= TERMP_SENTENCE; 1467 return(0); 1468 } 1469 1470 1471 /* ARGSUSED */ 1472 static void 1473 termp_lb_post(DECL_ARGS) 1474 { 1475 1476 if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags) 1477 term_newln(p); 1478 } 1479 1480 1481 /* ARGSUSED */ 1482 static int 1483 termp_ud_pre(DECL_ARGS) 1484 { 1485 1486 term_word(p, "currently under development."); 1487 p->flags |= TERMP_SENTENCE; 1488 return(0); 1489 } 1490 1491 1492 /* ARGSUSED */ 1493 static int 1494 termp_d1_pre(DECL_ARGS) 1495 { 1496 1497 if (MDOC_BLOCK != n->type) 1498 return(1); 1499 term_newln(p); 1500 p->offset += term_len(p, (INDENT + 1)); 1501 return(1); 1502 } 1503 1504 1505 /* ARGSUSED */ 1506 static void 1507 termp_d1_post(DECL_ARGS) 1508 { 1509 1510 if (MDOC_BLOCK != n->type) 1511 return; 1512 term_newln(p); 1513 } 1514 1515 1516 /* ARGSUSED */ 1517 static int 1518 termp_ft_pre(DECL_ARGS) 1519 { 1520 1521 /* NB: MDOC_LINE does not effect this! */ 1522 synopsis_pre(p, n); 1523 term_fontpush(p, TERMFONT_UNDER); 1524 return(1); 1525 } 1526 1527 1528 /* ARGSUSED */ 1529 static int 1530 termp_fn_pre(DECL_ARGS) 1531 { 1532 int pretty; 1533 1534 pretty = MDOC_SYNPRETTY & n->flags; 1535 1536 synopsis_pre(p, n); 1537 1538 if (NULL == (n = n->child)) 1539 return(0); 1540 1541 assert(MDOC_TEXT == n->type); 1542 term_fontpush(p, TERMFONT_BOLD); 1543 term_word(p, n->string); 1544 term_fontpop(p); 1545 1546 p->flags |= TERMP_NOSPACE; 1547 term_word(p, "("); 1548 p->flags |= TERMP_NOSPACE; 1549 1550 for (n = n->next; n; n = n->next) { 1551 assert(MDOC_TEXT == n->type); 1552 term_fontpush(p, TERMFONT_UNDER); 1553 term_word(p, n->string); 1554 term_fontpop(p); 1555 1556 if (n->next) { 1557 p->flags |= TERMP_NOSPACE; 1558 term_word(p, ","); 1559 } 1560 } 1561 1562 p->flags |= TERMP_NOSPACE; 1563 term_word(p, ")"); 1564 1565 if (pretty) { 1566 p->flags |= TERMP_NOSPACE; 1567 term_word(p, ";"); 1568 } 1569 1570 return(0); 1571 } 1572 1573 1574 /* ARGSUSED */ 1575 static int 1576 termp_fa_pre(DECL_ARGS) 1577 { 1578 const struct mdoc_node *nn; 1579 1580 if (n->parent->tok != MDOC_Fo) { 1581 term_fontpush(p, TERMFONT_UNDER); 1582 return(1); 1583 } 1584 1585 for (nn = n->child; nn; nn = nn->next) { 1586 term_fontpush(p, TERMFONT_UNDER); 1587 term_word(p, nn->string); 1588 term_fontpop(p); 1589 1590 if (nn->next) { 1591 p->flags |= TERMP_NOSPACE; 1592 term_word(p, ","); 1593 } 1594 } 1595 1596 if (n->child && n->next && n->next->tok == MDOC_Fa) { 1597 p->flags |= TERMP_NOSPACE; 1598 term_word(p, ","); 1599 } 1600 1601 return(0); 1602 } 1603 1604 1605 /* ARGSUSED */ 1606 static int 1607 termp_bd_pre(DECL_ARGS) 1608 { 1609 size_t tabwidth, rm, rmax; 1610 const struct mdoc_node *nn; 1611 1612 if (MDOC_BLOCK == n->type) { 1613 print_bvspace(p, n, n); 1614 return(1); 1615 } else if (MDOC_HEAD == n->type) 1616 return(0); 1617 1618 if (n->norm->Bd.offs) 1619 p->offset += a2offs(p, n->norm->Bd.offs); 1620 1621 /* 1622 * If -ragged or -filled are specified, the block does nothing 1623 * but change the indentation. If -unfilled or -literal are 1624 * specified, text is printed exactly as entered in the display: 1625 * for macro lines, a newline is appended to the line. Blank 1626 * lines are allowed. 1627 */ 1628 1629 if (DISP_literal != n->norm->Bd.type && 1630 DISP_unfilled != n->norm->Bd.type) 1631 return(1); 1632 1633 tabwidth = p->tabwidth; 1634 if (DISP_literal == n->norm->Bd.type) 1635 p->tabwidth = term_len(p, 8); 1636 1637 rm = p->rmargin; 1638 rmax = p->maxrmargin; 1639 p->rmargin = p->maxrmargin = TERM_MAXMARGIN; 1640 1641 for (nn = n->child; nn; nn = nn->next) { 1642 print_mdoc_node(p, pair, m, nn); 1643 /* 1644 * If the printed node flushes its own line, then we 1645 * needn't do it here as well. This is hacky, but the 1646 * notion of selective eoln whitespace is pretty dumb 1647 * anyway, so don't sweat it. 1648 */ 1649 switch (nn->tok) { 1650 case (MDOC_Sm): 1651 /* FALLTHROUGH */ 1652 case (MDOC_br): 1653 /* FALLTHROUGH */ 1654 case (MDOC_sp): 1655 /* FALLTHROUGH */ 1656 case (MDOC_Bl): 1657 /* FALLTHROUGH */ 1658 case (MDOC_D1): 1659 /* FALLTHROUGH */ 1660 case (MDOC_Dl): 1661 /* FALLTHROUGH */ 1662 case (MDOC_Lp): 1663 /* FALLTHROUGH */ 1664 case (MDOC_Pp): 1665 continue; 1666 default: 1667 break; 1668 } 1669 if (nn->next && nn->next->line == nn->line) 1670 continue; 1671 term_flushln(p); 1672 p->flags |= TERMP_NOSPACE; 1673 } 1674 1675 p->tabwidth = tabwidth; 1676 p->rmargin = rm; 1677 p->maxrmargin = rmax; 1678 return(0); 1679 } 1680 1681 1682 /* ARGSUSED */ 1683 static void 1684 termp_bd_post(DECL_ARGS) 1685 { 1686 size_t rm, rmax; 1687 1688 if (MDOC_BODY != n->type) 1689 return; 1690 1691 rm = p->rmargin; 1692 rmax = p->maxrmargin; 1693 1694 if (DISP_literal == n->norm->Bd.type || 1695 DISP_unfilled == n->norm->Bd.type) 1696 p->rmargin = p->maxrmargin = TERM_MAXMARGIN; 1697 1698 p->flags |= TERMP_NOSPACE; 1699 term_newln(p); 1700 1701 p->rmargin = rm; 1702 p->maxrmargin = rmax; 1703 } 1704 1705 1706 /* ARGSUSED */ 1707 static int 1708 termp_bx_pre(DECL_ARGS) 1709 { 1710 1711 if (NULL != (n = n->child)) { 1712 term_word(p, n->string); 1713 p->flags |= TERMP_NOSPACE; 1714 term_word(p, "BSD"); 1715 } else { 1716 term_word(p, "BSD"); 1717 return(0); 1718 } 1719 1720 if (NULL != (n = n->next)) { 1721 p->flags |= TERMP_NOSPACE; 1722 term_word(p, "-"); 1723 p->flags |= TERMP_NOSPACE; 1724 term_word(p, n->string); 1725 } 1726 1727 return(0); 1728 } 1729 1730 1731 /* ARGSUSED */ 1732 static int 1733 termp_xx_pre(DECL_ARGS) 1734 { 1735 const char *pp; 1736 int flags; 1737 1738 pp = NULL; 1739 switch (n->tok) { 1740 case (MDOC_Bsx): 1741 pp = "BSD/OS"; 1742 break; 1743 case (MDOC_Dx): 1744 pp = "DragonFly"; 1745 break; 1746 case (MDOC_Fx): 1747 pp = "FreeBSD"; 1748 break; 1749 case (MDOC_Nx): 1750 pp = "NetBSD"; 1751 break; 1752 case (MDOC_Ox): 1753 pp = "OpenBSD"; 1754 break; 1755 case (MDOC_Ux): 1756 pp = "UNIX"; 1757 break; 1758 default: 1759 break; 1760 } 1761 1762 term_word(p, pp); 1763 if (n->child) { 1764 flags = p->flags; 1765 p->flags |= TERMP_KEEP; 1766 term_word(p, n->child->string); 1767 p->flags = flags; 1768 } 1769 return(0); 1770 } 1771 1772 1773 /* ARGSUSED */ 1774 static int 1775 termp_igndelim_pre(DECL_ARGS) 1776 { 1777 1778 p->flags |= TERMP_IGNDELIM; 1779 return(1); 1780 } 1781 1782 1783 /* ARGSUSED */ 1784 static void 1785 termp_pf_post(DECL_ARGS) 1786 { 1787 1788 p->flags |= TERMP_NOSPACE; 1789 } 1790 1791 1792 /* ARGSUSED */ 1793 static int 1794 termp_ss_pre(DECL_ARGS) 1795 { 1796 1797 switch (n->type) { 1798 case (MDOC_BLOCK): 1799 term_newln(p); 1800 if (n->prev) 1801 term_vspace(p); 1802 break; 1803 case (MDOC_HEAD): 1804 term_fontpush(p, TERMFONT_BOLD); 1805 p->offset = term_len(p, HALFINDENT); 1806 break; 1807 default: 1808 break; 1809 } 1810 1811 return(1); 1812 } 1813 1814 1815 /* ARGSUSED */ 1816 static void 1817 termp_ss_post(DECL_ARGS) 1818 { 1819 1820 if (MDOC_HEAD == n->type) 1821 term_newln(p); 1822 } 1823 1824 1825 /* ARGSUSED */ 1826 static int 1827 termp_cd_pre(DECL_ARGS) 1828 { 1829 1830 synopsis_pre(p, n); 1831 term_fontpush(p, TERMFONT_BOLD); 1832 return(1); 1833 } 1834 1835 1836 /* ARGSUSED */ 1837 static int 1838 termp_in_pre(DECL_ARGS) 1839 { 1840 1841 synopsis_pre(p, n); 1842 1843 if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) { 1844 term_fontpush(p, TERMFONT_BOLD); 1845 term_word(p, "#include"); 1846 term_word(p, "<"); 1847 } else { 1848 term_word(p, "<"); 1849 term_fontpush(p, TERMFONT_UNDER); 1850 } 1851 1852 p->flags |= TERMP_NOSPACE; 1853 return(1); 1854 } 1855 1856 1857 /* ARGSUSED */ 1858 static void 1859 termp_in_post(DECL_ARGS) 1860 { 1861 1862 if (MDOC_SYNPRETTY & n->flags) 1863 term_fontpush(p, TERMFONT_BOLD); 1864 1865 p->flags |= TERMP_NOSPACE; 1866 term_word(p, ">"); 1867 1868 if (MDOC_SYNPRETTY & n->flags) 1869 term_fontpop(p); 1870 } 1871 1872 1873 /* ARGSUSED */ 1874 static int 1875 termp_sp_pre(DECL_ARGS) 1876 { 1877 size_t i, len; 1878 1879 switch (n->tok) { 1880 case (MDOC_sp): 1881 len = n->child ? a2height(p, n->child->string) : 1; 1882 break; 1883 case (MDOC_br): 1884 len = 0; 1885 break; 1886 default: 1887 len = 1; 1888 break; 1889 } 1890 1891 if (0 == len) 1892 term_newln(p); 1893 for (i = 0; i < len; i++) 1894 term_vspace(p); 1895 1896 return(0); 1897 } 1898 1899 1900 /* ARGSUSED */ 1901 static int 1902 termp_quote_pre(DECL_ARGS) 1903 { 1904 1905 if (MDOC_BODY != n->type && MDOC_ELEM != n->type) 1906 return(1); 1907 1908 switch (n->tok) { 1909 case (MDOC_Ao): 1910 /* FALLTHROUGH */ 1911 case (MDOC_Aq): 1912 term_word(p, "<"); 1913 break; 1914 case (MDOC_Bro): 1915 /* FALLTHROUGH */ 1916 case (MDOC_Brq): 1917 term_word(p, "{"); 1918 break; 1919 case (MDOC_Oo): 1920 /* FALLTHROUGH */ 1921 case (MDOC_Op): 1922 /* FALLTHROUGH */ 1923 case (MDOC_Bo): 1924 /* FALLTHROUGH */ 1925 case (MDOC_Bq): 1926 term_word(p, "["); 1927 break; 1928 case (MDOC_Do): 1929 /* FALLTHROUGH */ 1930 case (MDOC_Dq): 1931 term_word(p, "``"); 1932 break; 1933 case (MDOC_Po): 1934 /* FALLTHROUGH */ 1935 case (MDOC_Pq): 1936 term_word(p, "("); 1937 break; 1938 case (MDOC__T): 1939 /* FALLTHROUGH */ 1940 case (MDOC_Qo): 1941 /* FALLTHROUGH */ 1942 case (MDOC_Qq): 1943 term_word(p, "\""); 1944 break; 1945 case (MDOC_Ql): 1946 /* FALLTHROUGH */ 1947 case (MDOC_So): 1948 /* FALLTHROUGH */ 1949 case (MDOC_Sq): 1950 term_word(p, "`"); 1951 break; 1952 default: 1953 abort(); 1954 /* NOTREACHED */ 1955 } 1956 1957 p->flags |= TERMP_NOSPACE; 1958 return(1); 1959 } 1960 1961 1962 /* ARGSUSED */ 1963 static void 1964 termp_quote_post(DECL_ARGS) 1965 { 1966 1967 if (MDOC_BODY != n->type && MDOC_ELEM != n->type) 1968 return; 1969 1970 p->flags |= TERMP_NOSPACE; 1971 1972 switch (n->tok) { 1973 case (MDOC_Ao): 1974 /* FALLTHROUGH */ 1975 case (MDOC_Aq): 1976 term_word(p, ">"); 1977 break; 1978 case (MDOC_Bro): 1979 /* FALLTHROUGH */ 1980 case (MDOC_Brq): 1981 term_word(p, "}"); 1982 break; 1983 case (MDOC_Oo): 1984 /* FALLTHROUGH */ 1985 case (MDOC_Op): 1986 /* FALLTHROUGH */ 1987 case (MDOC_Bo): 1988 /* FALLTHROUGH */ 1989 case (MDOC_Bq): 1990 term_word(p, "]"); 1991 break; 1992 case (MDOC_Do): 1993 /* FALLTHROUGH */ 1994 case (MDOC_Dq): 1995 term_word(p, "''"); 1996 break; 1997 case (MDOC_Po): 1998 /* FALLTHROUGH */ 1999 case (MDOC_Pq): 2000 term_word(p, ")"); 2001 break; 2002 case (MDOC__T): 2003 /* FALLTHROUGH */ 2004 case (MDOC_Qo): 2005 /* FALLTHROUGH */ 2006 case (MDOC_Qq): 2007 term_word(p, "\""); 2008 break; 2009 case (MDOC_Ql): 2010 /* FALLTHROUGH */ 2011 case (MDOC_So): 2012 /* FALLTHROUGH */ 2013 case (MDOC_Sq): 2014 term_word(p, "'"); 2015 break; 2016 default: 2017 abort(); 2018 /* NOTREACHED */ 2019 } 2020 } 2021 2022 2023 /* ARGSUSED */ 2024 static int 2025 termp_fo_pre(DECL_ARGS) 2026 { 2027 2028 if (MDOC_BLOCK == n->type) { 2029 synopsis_pre(p, n); 2030 return(1); 2031 } else if (MDOC_BODY == n->type) { 2032 p->flags |= TERMP_NOSPACE; 2033 term_word(p, "("); 2034 p->flags |= TERMP_NOSPACE; 2035 return(1); 2036 } 2037 2038 if (NULL == n->child) 2039 return(0); 2040 2041 /* XXX: we drop non-initial arguments as per groff. */ 2042 2043 assert(n->child->string); 2044 term_fontpush(p, TERMFONT_BOLD); 2045 term_word(p, n->child->string); 2046 return(0); 2047 } 2048 2049 2050 /* ARGSUSED */ 2051 static void 2052 termp_fo_post(DECL_ARGS) 2053 { 2054 2055 if (MDOC_BODY != n->type) 2056 return; 2057 2058 p->flags |= TERMP_NOSPACE; 2059 term_word(p, ")"); 2060 2061 if (MDOC_SYNPRETTY & n->flags) { 2062 p->flags |= TERMP_NOSPACE; 2063 term_word(p, ";"); 2064 } 2065 } 2066 2067 2068 /* ARGSUSED */ 2069 static int 2070 termp_bf_pre(DECL_ARGS) 2071 { 2072 2073 if (MDOC_HEAD == n->type) 2074 return(0); 2075 else if (MDOC_BLOCK != n->type) 2076 return(1); 2077 2078 if (FONT_Em == n->norm->Bf.font) 2079 term_fontpush(p, TERMFONT_UNDER); 2080 else if (FONT_Sy == n->norm->Bf.font) 2081 term_fontpush(p, TERMFONT_BOLD); 2082 else 2083 term_fontpush(p, TERMFONT_NONE); 2084 2085 return(1); 2086 } 2087 2088 2089 /* ARGSUSED */ 2090 static int 2091 termp_sm_pre(DECL_ARGS) 2092 { 2093 2094 assert(n->child && MDOC_TEXT == n->child->type); 2095 if (0 == strcmp("on", n->child->string)) { 2096 if (p->col) 2097 p->flags &= ~TERMP_NOSPACE; 2098 p->flags &= ~TERMP_NONOSPACE; 2099 } else 2100 p->flags |= TERMP_NONOSPACE; 2101 2102 return(0); 2103 } 2104 2105 2106 /* ARGSUSED */ 2107 static int 2108 termp_ap_pre(DECL_ARGS) 2109 { 2110 2111 p->flags |= TERMP_NOSPACE; 2112 term_word(p, "'"); 2113 p->flags |= TERMP_NOSPACE; 2114 return(1); 2115 } 2116 2117 2118 /* ARGSUSED */ 2119 static void 2120 termp____post(DECL_ARGS) 2121 { 2122 2123 /* 2124 * Handle lists of authors. In general, print each followed by 2125 * a comma. Don't print the comma if there are only two 2126 * authors. 2127 */ 2128 if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok) 2129 if (NULL == n->next->next || MDOC__A != n->next->next->tok) 2130 if (NULL == n->prev || MDOC__A != n->prev->tok) 2131 return; 2132 2133 /* TODO: %U. */ 2134 2135 if (NULL == n->parent || MDOC_Rs != n->parent->tok) 2136 return; 2137 2138 p->flags |= TERMP_NOSPACE; 2139 if (NULL == n->next) { 2140 term_word(p, "."); 2141 p->flags |= TERMP_SENTENCE; 2142 } else 2143 term_word(p, ","); 2144 } 2145 2146 2147 /* ARGSUSED */ 2148 static int 2149 termp_li_pre(DECL_ARGS) 2150 { 2151 2152 term_fontpush(p, TERMFONT_NONE); 2153 return(1); 2154 } 2155 2156 2157 /* ARGSUSED */ 2158 static int 2159 termp_lk_pre(DECL_ARGS) 2160 { 2161 const struct mdoc_node *nn, *sv; 2162 2163 term_fontpush(p, TERMFONT_UNDER); 2164 2165 nn = sv = n->child; 2166 2167 if (NULL == nn || NULL == nn->next) 2168 return(1); 2169 2170 for (nn = nn->next; nn; nn = nn->next) 2171 term_word(p, nn->string); 2172 2173 term_fontpop(p); 2174 2175 p->flags |= TERMP_NOSPACE; 2176 term_word(p, ":"); 2177 2178 term_fontpush(p, TERMFONT_BOLD); 2179 term_word(p, sv->string); 2180 term_fontpop(p); 2181 2182 return(0); 2183 } 2184 2185 2186 /* ARGSUSED */ 2187 static int 2188 termp_bk_pre(DECL_ARGS) 2189 { 2190 2191 switch (n->type) { 2192 case (MDOC_BLOCK): 2193 break; 2194 case (MDOC_HEAD): 2195 return(0); 2196 case (MDOC_BODY): 2197 if (n->parent->args || 0 == n->prev->nchild) 2198 p->flags |= TERMP_PREKEEP; 2199 break; 2200 default: 2201 abort(); 2202 /* NOTREACHED */ 2203 } 2204 2205 return(1); 2206 } 2207 2208 2209 /* ARGSUSED */ 2210 static void 2211 termp_bk_post(DECL_ARGS) 2212 { 2213 2214 if (MDOC_BODY == n->type) 2215 p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); 2216 } 2217 2218 /* ARGSUSED */ 2219 static void 2220 termp__t_post(DECL_ARGS) 2221 { 2222 2223 /* 2224 * If we're in an `Rs' and there's a journal present, then quote 2225 * us instead of underlining us (for disambiguation). 2226 */ 2227 if (n->parent && MDOC_Rs == n->parent->tok && 2228 n->parent->norm->Rs.quote_T) 2229 termp_quote_post(p, pair, m, n); 2230 2231 termp____post(p, pair, m, n); 2232 } 2233 2234 /* ARGSUSED */ 2235 static int 2236 termp__t_pre(DECL_ARGS) 2237 { 2238 2239 /* 2240 * If we're in an `Rs' and there's a journal present, then quote 2241 * us instead of underlining us (for disambiguation). 2242 */ 2243 if (n->parent && MDOC_Rs == n->parent->tok && 2244 n->parent->norm->Rs.quote_T) 2245 return(termp_quote_pre(p, pair, m, n)); 2246 2247 term_fontpush(p, TERMFONT_UNDER); 2248 return(1); 2249 } 2250 2251 /* ARGSUSED */ 2252 static int 2253 termp_under_pre(DECL_ARGS) 2254 { 2255 2256 term_fontpush(p, TERMFONT_UNDER); 2257 return(1); 2258 } 2259