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