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