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