1 /* $Id: mdoc_term.c,v 1.383 2023/11/13 19:13:01 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2010, 2012-2020, 2022 Ingo Schwarze <schwarze@openbsd.org> 4 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 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 * Plain text formatter for mdoc(7), used by mandoc(1) 20 * for ASCII, UTF-8, PostScript, and PDF output. 21 */ 22 #include "config.h" 23 24 #include <sys/types.h> 25 26 #include <assert.h> 27 #include <ctype.h> 28 #include <limits.h> 29 #include <stdint.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 34 #include "mandoc_aux.h" 35 #include "roff.h" 36 #include "mdoc.h" 37 #include "out.h" 38 #include "term.h" 39 #include "term_tag.h" 40 #include "main.h" 41 42 struct termpair { 43 struct termpair *ppair; 44 int count; 45 }; 46 47 #define DECL_ARGS struct termp *p, \ 48 struct termpair *pair, \ 49 const struct roff_meta *meta, \ 50 struct roff_node *n 51 52 struct mdoc_term_act { 53 int (*pre)(DECL_ARGS); 54 void (*post)(DECL_ARGS); 55 }; 56 57 static int a2width(const struct termp *, const char *); 58 59 static void print_bvspace(struct termp *, 60 struct roff_node *, struct roff_node *); 61 static void print_mdoc_node(DECL_ARGS); 62 static void print_mdoc_nodelist(DECL_ARGS); 63 static void print_mdoc_head(struct termp *, const struct roff_meta *); 64 static void print_mdoc_foot(struct termp *, const struct roff_meta *); 65 static void synopsis_pre(struct termp *, struct roff_node *); 66 67 static void termp____post(DECL_ARGS); 68 static void termp__t_post(DECL_ARGS); 69 static void termp_bd_post(DECL_ARGS); 70 static void termp_bk_post(DECL_ARGS); 71 static void termp_bl_post(DECL_ARGS); 72 static void termp_eo_post(DECL_ARGS); 73 static void termp_fd_post(DECL_ARGS); 74 static void termp_fo_post(DECL_ARGS); 75 static void termp_in_post(DECL_ARGS); 76 static void termp_it_post(DECL_ARGS); 77 static void termp_lb_post(DECL_ARGS); 78 static void termp_nm_post(DECL_ARGS); 79 static void termp_pf_post(DECL_ARGS); 80 static void termp_quote_post(DECL_ARGS); 81 static void termp_sh_post(DECL_ARGS); 82 static void termp_ss_post(DECL_ARGS); 83 static void termp_xx_post(DECL_ARGS); 84 85 static int termp__a_pre(DECL_ARGS); 86 static int termp__t_pre(DECL_ARGS); 87 static int termp_abort_pre(DECL_ARGS); 88 static int termp_an_pre(DECL_ARGS); 89 static int termp_ap_pre(DECL_ARGS); 90 static int termp_bd_pre(DECL_ARGS); 91 static int termp_bf_pre(DECL_ARGS); 92 static int termp_bk_pre(DECL_ARGS); 93 static int termp_bl_pre(DECL_ARGS); 94 static int termp_bold_pre(DECL_ARGS); 95 static int termp_d1_pre(DECL_ARGS); 96 static int termp_eo_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_lk_pre(DECL_ARGS); 108 static int termp_nd_pre(DECL_ARGS); 109 static int termp_nm_pre(DECL_ARGS); 110 static int termp_ns_pre(DECL_ARGS); 111 static int termp_quote_pre(DECL_ARGS); 112 static int termp_rs_pre(DECL_ARGS); 113 static int termp_sh_pre(DECL_ARGS); 114 static int termp_skip_pre(DECL_ARGS); 115 static int termp_sm_pre(DECL_ARGS); 116 static int termp_pp_pre(DECL_ARGS); 117 static int termp_ss_pre(DECL_ARGS); 118 static int termp_under_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 mdoc_term_act mdoc_term_acts[MDOC_MAX - MDOC_Dd] = { 124 { NULL, NULL }, /* Dd */ 125 { NULL, NULL }, /* Dt */ 126 { NULL, NULL }, /* Os */ 127 { termp_sh_pre, termp_sh_post }, /* Sh */ 128 { termp_ss_pre, termp_ss_post }, /* Ss */ 129 { termp_pp_pre, NULL }, /* Pp */ 130 { termp_d1_pre, termp_bl_post }, /* D1 */ 131 { termp_d1_pre, termp_bl_post }, /* Dl */ 132 { termp_bd_pre, termp_bd_post }, /* Bd */ 133 { NULL, NULL }, /* Ed */ 134 { termp_bl_pre, termp_bl_post }, /* Bl */ 135 { NULL, NULL }, /* El */ 136 { termp_it_pre, termp_it_post }, /* It */ 137 { termp_under_pre, NULL }, /* Ad */ 138 { termp_an_pre, NULL }, /* An */ 139 { termp_ap_pre, NULL }, /* Ap */ 140 { termp_under_pre, NULL }, /* Ar */ 141 { termp_fd_pre, NULL }, /* Cd */ 142 { termp_bold_pre, NULL }, /* Cm */ 143 { termp_li_pre, 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_abort_pre, NULL }, /* Ot */ 159 { termp_under_pre, NULL }, /* Pa */ 160 { termp_ex_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, termp_xx_post }, /* Bsx */ 185 { NULL, NULL }, /* Bx */ 186 { termp_skip_pre, 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_eo_pre, termp_eo_post }, /* Eo */ 194 { termp_xx_pre, termp_xx_post }, /* Fx */ 195 { termp_bold_pre, NULL }, /* Ms */ 196 { termp_li_pre, NULL }, /* No */ 197 { termp_ns_pre, NULL }, /* Ns */ 198 { termp_xx_pre, termp_xx_post }, /* Nx */ 199 { termp_xx_pre, termp_xx_post }, /* 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, termp_xx_post }, /* 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 { NULL, NULL }, /* Bt */ 227 { NULL, NULL }, /* Hf */ 228 { termp_under_pre, NULL }, /* Fr */ 229 { NULL, NULL }, /* Ud */ 230 { NULL, termp_lb_post }, /* Lb */ 231 { termp_abort_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_skip_pre, NULL }, /* Es */ 239 { termp_quote_pre, termp_quote_post }, /* En */ 240 { termp_xx_pre, termp_xx_post }, /* Dx */ 241 { NULL, termp____post }, /* %Q */ 242 { NULL, termp____post }, /* %U */ 243 { NULL, NULL }, /* Ta */ 244 { termp_skip_pre, NULL }, /* Tg */ 245 }; 246 247 248 void 249 terminal_mdoc(void *arg, const struct roff_meta *mdoc) 250 { 251 struct roff_node *n, *nn; 252 struct termp *p; 253 254 p = (struct termp *)arg; 255 p->tcol->rmargin = p->maxrmargin = p->defrmargin; 256 term_tab_set(p, NULL); 257 term_tab_set(p, "T"); 258 term_tab_set(p, ".5i"); 259 260 n = mdoc->first->child; 261 if (p->synopsisonly) { 262 for (nn = NULL; n != NULL; n = n->next) { 263 if (n->tok != MDOC_Sh) 264 continue; 265 if (n->sec == SEC_SYNOPSIS) 266 break; 267 if (nn == NULL && n->sec == SEC_NAME) 268 nn = n; 269 } 270 if (n == NULL) 271 n = nn; 272 p->flags |= TERMP_NOSPACE; 273 if (n != NULL && (n = n->child->next->child) != NULL) 274 print_mdoc_nodelist(p, NULL, mdoc, n); 275 term_newln(p); 276 } else { 277 term_begin(p, print_mdoc_head, print_mdoc_foot, mdoc); 278 while (n != NULL && 279 (n->type == ROFFT_COMMENT || 280 n->flags & NODE_NOPRT)) 281 n = n->next; 282 if (n != NULL) { 283 if (n->tok != MDOC_Sh) 284 term_vspace(p); 285 print_mdoc_nodelist(p, NULL, mdoc, n); 286 } 287 term_end(p); 288 } 289 } 290 291 static void 292 print_mdoc_nodelist(DECL_ARGS) 293 { 294 while (n != NULL) { 295 print_mdoc_node(p, pair, meta, n); 296 n = n->next; 297 } 298 } 299 300 static void 301 print_mdoc_node(DECL_ARGS) 302 { 303 const struct mdoc_term_act *act; 304 struct termpair npair; 305 size_t offset, rmargin; 306 int chld; 307 308 /* 309 * In no-fill mode, break the output line at the beginning 310 * of new input lines except after \c, and nowhere else. 311 */ 312 313 if (n->flags & NODE_NOFILL) { 314 if (n->flags & NODE_LINE && 315 (p->flags & TERMP_NONEWLINE) == 0) 316 term_newln(p); 317 p->flags |= TERMP_BRNEVER; 318 } else { 319 if (n->flags & NODE_LINE) 320 term_tab_ref(p); 321 p->flags &= ~TERMP_BRNEVER; 322 } 323 324 if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT) 325 return; 326 327 chld = 1; 328 offset = p->tcol->offset; 329 rmargin = p->tcol->rmargin; 330 n->flags &= ~NODE_ENDED; 331 n->prev_font = p->fonti; 332 333 memset(&npair, 0, sizeof(struct termpair)); 334 npair.ppair = pair; 335 336 if (n->flags & NODE_ID && n->tok != MDOC_Pp && 337 (n->tok != MDOC_It || n->type != ROFFT_BLOCK)) 338 term_tag_write(n, p->line); 339 340 /* 341 * Keeps only work until the end of a line. If a keep was 342 * invoked in a prior line, revert it to PREKEEP. 343 */ 344 345 if (p->flags & TERMP_KEEP && n->flags & NODE_LINE) { 346 p->flags &= ~TERMP_KEEP; 347 p->flags |= TERMP_PREKEEP; 348 } 349 350 /* 351 * After the keep flags have been set up, we may now 352 * produce output. Note that some pre-handlers do so. 353 */ 354 355 act = NULL; 356 switch (n->type) { 357 case ROFFT_TEXT: 358 if (n->flags & NODE_LINE) { 359 switch (*n->string) { 360 case '\0': 361 if (p->flags & TERMP_NONEWLINE) 362 term_newln(p); 363 else 364 term_vspace(p); 365 return; 366 case ' ': 367 if ((p->flags & TERMP_NONEWLINE) == 0) 368 term_newln(p); 369 break; 370 default: 371 break; 372 } 373 } 374 if (NODE_DELIMC & n->flags) 375 p->flags |= TERMP_NOSPACE; 376 term_word(p, n->string); 377 if (NODE_DELIMO & n->flags) 378 p->flags |= TERMP_NOSPACE; 379 break; 380 case ROFFT_EQN: 381 if ( ! (n->flags & NODE_LINE)) 382 p->flags |= TERMP_NOSPACE; 383 term_eqn(p, n->eqn); 384 if (n->next != NULL && ! (n->next->flags & NODE_LINE)) 385 p->flags |= TERMP_NOSPACE; 386 break; 387 case ROFFT_TBL: 388 if (p->tbl.cols == NULL) 389 term_newln(p); 390 term_tbl(p, n->span); 391 break; 392 default: 393 if (n->tok < ROFF_MAX) { 394 roff_term_pre(p, n); 395 return; 396 } 397 assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX); 398 act = mdoc_term_acts + (n->tok - MDOC_Dd); 399 if (act->pre != NULL && 400 (n->end == ENDBODY_NOT || n->child != NULL)) 401 chld = (*act->pre)(p, &npair, meta, n); 402 break; 403 } 404 405 if (chld && n->child) 406 print_mdoc_nodelist(p, &npair, meta, n->child); 407 408 term_fontpopq(p, 409 (ENDBODY_NOT == n->end ? n : n->body)->prev_font); 410 411 switch (n->type) { 412 case ROFFT_TEXT: 413 break; 414 case ROFFT_TBL: 415 break; 416 case ROFFT_EQN: 417 break; 418 default: 419 if (act->post == NULL || n->flags & NODE_ENDED) 420 break; 421 (void)(*act->post)(p, &npair, meta, n); 422 423 /* 424 * Explicit end tokens not only call the post 425 * handler, but also tell the respective block 426 * that it must not call the post handler again. 427 */ 428 if (ENDBODY_NOT != n->end) 429 n->body->flags |= NODE_ENDED; 430 break; 431 } 432 433 if (NODE_EOS & n->flags) 434 p->flags |= TERMP_SENTENCE; 435 436 if (n->type != ROFFT_TEXT) 437 p->tcol->offset = offset; 438 p->tcol->rmargin = rmargin; 439 } 440 441 static void 442 print_mdoc_foot(struct termp *p, const struct roff_meta *meta) 443 { 444 size_t sz; 445 446 term_fontrepl(p, TERMFONT_NONE); 447 448 /* 449 * Output the footer in new-groff style, that is, three columns 450 * with the middle being the manual date and flanking columns 451 * being the operating system: 452 * 453 * SYSTEM DATE SYSTEM 454 */ 455 456 term_vspace(p); 457 458 p->tcol->offset = 0; 459 sz = term_strlen(p, meta->date); 460 p->tcol->rmargin = p->maxrmargin > sz ? 461 (p->maxrmargin + term_len(p, 1) - sz) / 2 : 0; 462 p->trailspace = 1; 463 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; 464 465 term_word(p, meta->os); 466 term_flushln(p); 467 468 p->tcol->offset = p->tcol->rmargin; 469 sz = term_strlen(p, meta->os); 470 p->tcol->rmargin = p->maxrmargin > sz ? p->maxrmargin - sz : 0; 471 p->flags |= TERMP_NOSPACE; 472 473 term_word(p, meta->date); 474 term_flushln(p); 475 476 p->tcol->offset = p->tcol->rmargin; 477 p->tcol->rmargin = p->maxrmargin; 478 p->trailspace = 0; 479 p->flags &= ~TERMP_NOBREAK; 480 p->flags |= TERMP_NOSPACE; 481 482 term_word(p, meta->os); 483 term_flushln(p); 484 485 p->tcol->offset = 0; 486 p->tcol->rmargin = p->maxrmargin; 487 p->flags = 0; 488 } 489 490 static void 491 print_mdoc_head(struct termp *p, const struct roff_meta *meta) 492 { 493 char *volume, *title; 494 size_t vollen, titlen; 495 496 /* 497 * The header is strange. It has three components, which are 498 * really two with the first duplicated. It goes like this: 499 * 500 * IDENTIFIER TITLE IDENTIFIER 501 * 502 * The IDENTIFIER is NAME(SECTION), which is the command-name 503 * (if given, or "unknown" if not) followed by the manual page 504 * section. These are given in `Dt'. The TITLE is a free-form 505 * string depending on the manual volume. If not specified, it 506 * switches on the manual section. 507 */ 508 509 assert(meta->vol); 510 if (NULL == meta->arch) 511 volume = mandoc_strdup(meta->vol); 512 else 513 mandoc_asprintf(&volume, "%s (%s)", 514 meta->vol, meta->arch); 515 vollen = term_strlen(p, volume); 516 517 if (NULL == meta->msec) 518 title = mandoc_strdup(meta->title); 519 else 520 mandoc_asprintf(&title, "%s(%s)", 521 meta->title, meta->msec); 522 titlen = term_strlen(p, title); 523 524 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; 525 p->trailspace = 1; 526 p->tcol->offset = 0; 527 p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ? 528 (p->maxrmargin - vollen + term_len(p, 1)) / 2 : 529 vollen < p->maxrmargin ? p->maxrmargin - vollen : 0; 530 531 term_word(p, title); 532 term_flushln(p); 533 534 p->flags |= TERMP_NOSPACE; 535 p->tcol->offset = p->tcol->rmargin; 536 p->tcol->rmargin = p->tcol->offset + vollen + titlen < 537 p->maxrmargin ? p->maxrmargin - titlen : p->maxrmargin; 538 539 term_word(p, volume); 540 term_flushln(p); 541 542 p->flags &= ~TERMP_NOBREAK; 543 p->trailspace = 0; 544 if (p->tcol->rmargin + titlen <= p->maxrmargin) { 545 p->flags |= TERMP_NOSPACE; 546 p->tcol->offset = p->tcol->rmargin; 547 p->tcol->rmargin = p->maxrmargin; 548 term_word(p, title); 549 term_flushln(p); 550 } 551 552 p->flags &= ~TERMP_NOSPACE; 553 p->tcol->offset = 0; 554 p->tcol->rmargin = p->maxrmargin; 555 free(title); 556 free(volume); 557 } 558 559 static int 560 a2width(const struct termp *p, const char *v) 561 { 562 struct roffsu su; 563 const char *end; 564 565 end = a2roffsu(v, &su, SCALE_MAX); 566 if (end == NULL || *end != '\0') { 567 su.unit = SCALE_EN; 568 su.scale = term_strlen(p, v) / term_strlen(p, "0"); 569 } 570 return term_hen(p, &su); 571 } 572 573 /* 574 * Determine how much space to print out before block elements of `It' 575 * (and thus `Bl') and `Bd'. And then go ahead and print that space, 576 * too. 577 */ 578 static void 579 print_bvspace(struct termp *p, struct roff_node *bl, struct roff_node *n) 580 { 581 struct roff_node *nn; 582 583 term_newln(p); 584 585 if ((bl->tok == MDOC_Bd && bl->norm->Bd.comp) || 586 (bl->tok == MDOC_Bl && bl->norm->Bl.comp)) 587 return; 588 589 /* Do not vspace directly after Ss/Sh. */ 590 591 nn = n; 592 while (roff_node_prev(nn) == NULL) { 593 do { 594 nn = nn->parent; 595 if (nn->type == ROFFT_ROOT) 596 return; 597 } while (nn->type != ROFFT_BLOCK); 598 if (nn->tok == MDOC_Sh || nn->tok == MDOC_Ss) 599 return; 600 if (nn->tok == MDOC_It && 601 nn->parent->parent->norm->Bl.type != LIST_item) 602 break; 603 } 604 605 /* 606 * No vertical space after: 607 * items in .Bl -column 608 * items without a body in .Bl -diag 609 */ 610 611 if (bl->tok != MDOC_Bl || 612 n->prev == NULL || n->prev->tok != MDOC_It || 613 (bl->norm->Bl.type != LIST_column && 614 (bl->norm->Bl.type != LIST_diag || 615 n->prev->body->child != NULL))) 616 term_vspace(p); 617 } 618 619 620 static int 621 termp_it_pre(DECL_ARGS) 622 { 623 struct roffsu su; 624 char buf[24]; 625 const struct roff_node *bl, *nn; 626 size_t ncols, dcol; 627 int i, offset, width; 628 enum mdoc_list type; 629 630 if (n->type == ROFFT_BLOCK) { 631 print_bvspace(p, n->parent->parent, n); 632 if (n->flags & NODE_ID) 633 term_tag_write(n, p->line); 634 return 1; 635 } 636 637 bl = n->parent->parent->parent; 638 type = bl->norm->Bl.type; 639 640 /* 641 * Defaults for specific list types. 642 */ 643 644 switch (type) { 645 case LIST_bullet: 646 case LIST_dash: 647 case LIST_hyphen: 648 case LIST_enum: 649 width = term_len(p, 2); 650 break; 651 case LIST_hang: 652 case LIST_tag: 653 width = term_len(p, 8); 654 break; 655 case LIST_column: 656 width = term_len(p, 10); 657 break; 658 default: 659 width = 0; 660 break; 661 } 662 offset = 0; 663 664 /* 665 * First calculate width and offset. This is pretty easy unless 666 * we're a -column list, in which case all prior columns must 667 * be accounted for. 668 */ 669 670 if (bl->norm->Bl.offs != NULL) { 671 offset = a2width(p, bl->norm->Bl.offs); 672 if (offset < 0 && (size_t)(-offset) > p->tcol->offset) 673 offset = -p->tcol->offset; 674 else if (offset > SHRT_MAX) 675 offset = 0; 676 } 677 678 switch (type) { 679 case LIST_column: 680 if (n->type == ROFFT_HEAD) 681 break; 682 683 /* 684 * Imitate groff's column handling: 685 * - For each earlier column, add its width. 686 * - For less than 5 columns, add four more blanks per 687 * column. 688 * - For exactly 5 columns, add three more blank per 689 * column. 690 * - For more than 5 columns, add only one column. 691 */ 692 ncols = bl->norm->Bl.ncols; 693 dcol = ncols < 5 ? term_len(p, 4) : 694 ncols == 5 ? term_len(p, 3) : term_len(p, 1); 695 696 /* 697 * Calculate the offset by applying all prior ROFFT_BODY, 698 * so we stop at the ROFFT_HEAD (nn->prev == NULL). 699 */ 700 701 for (i = 0, nn = n->prev; 702 nn->prev && i < (int)ncols; 703 nn = nn->prev, i++) { 704 su.unit = SCALE_EN; 705 su.scale = term_strlen(p, bl->norm->Bl.cols[i]) / 706 term_strlen(p, "0"); 707 offset += term_hen(p, &su) + dcol; 708 } 709 710 /* 711 * When exceeding the declared number of columns, leave 712 * the remaining widths at 0. This will later be 713 * adjusted to the default width of 10, or, for the last 714 * column, stretched to the right margin. 715 */ 716 if (i >= (int)ncols) 717 break; 718 719 /* 720 * Use the declared column widths, extended as explained 721 * in the preceding paragraph. 722 */ 723 su.unit = SCALE_EN; 724 su.scale = term_strlen(p, bl->norm->Bl.cols[i]) / 725 term_strlen(p, "0"); 726 width = term_hen(p, &su) + dcol; 727 break; 728 default: 729 if (NULL == bl->norm->Bl.width) 730 break; 731 732 /* 733 * Note: buffer the width by 2, which is groff's magic 734 * number for buffering single arguments. See the above 735 * handling for column for how this changes. 736 */ 737 width = a2width(p, bl->norm->Bl.width) + term_len(p, 2); 738 if (width < 0 && (size_t)(-width) > p->tcol->offset) 739 width = -p->tcol->offset; 740 else if (width > SHRT_MAX) 741 width = 0; 742 break; 743 } 744 745 /* 746 * Whitespace control. Inset bodies need an initial space, 747 * while diagonal bodies need two. 748 */ 749 750 p->flags |= TERMP_NOSPACE; 751 752 switch (type) { 753 case LIST_diag: 754 if (n->type == ROFFT_BODY) 755 term_word(p, "\\ \\ "); 756 break; 757 case LIST_inset: 758 if (n->type == ROFFT_BODY && n->parent->head->child != NULL) 759 term_word(p, "\\ "); 760 break; 761 default: 762 break; 763 } 764 765 p->flags |= TERMP_NOSPACE; 766 767 switch (type) { 768 case LIST_diag: 769 if (n->type == ROFFT_HEAD) 770 term_fontpush(p, TERMFONT_BOLD); 771 break; 772 default: 773 break; 774 } 775 776 /* 777 * Pad and break control. This is the tricky part. These flags 778 * are documented in term_flushln() in term.c. Note that we're 779 * going to unset all of these flags in termp_it_post() when we 780 * exit. 781 */ 782 783 switch (type) { 784 case LIST_enum: 785 case LIST_bullet: 786 case LIST_dash: 787 case LIST_hyphen: 788 if (n->type == ROFFT_HEAD) { 789 p->flags |= TERMP_NOBREAK | TERMP_HANG; 790 p->trailspace = 1; 791 } else if (width <= (int)term_len(p, 2)) 792 p->flags |= TERMP_NOPAD; 793 break; 794 case LIST_hang: 795 if (n->type != ROFFT_HEAD) 796 break; 797 p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG; 798 p->trailspace = 1; 799 break; 800 case LIST_tag: 801 if (n->type != ROFFT_HEAD) 802 break; 803 804 p->flags |= TERMP_NOBREAK | TERMP_BRTRSP | TERMP_BRIND; 805 p->trailspace = 2; 806 807 if (NULL == n->next || NULL == n->next->child) 808 p->flags |= TERMP_HANG; 809 break; 810 case LIST_column: 811 if (n->type == ROFFT_HEAD) 812 break; 813 814 if (NULL == n->next) { 815 p->flags &= ~TERMP_NOBREAK; 816 p->trailspace = 0; 817 } else { 818 p->flags |= TERMP_NOBREAK; 819 p->trailspace = 1; 820 } 821 822 break; 823 case LIST_diag: 824 if (n->type != ROFFT_HEAD) 825 break; 826 p->flags |= TERMP_NOBREAK | TERMP_BRIND; 827 p->trailspace = 1; 828 break; 829 default: 830 break; 831 } 832 833 /* 834 * Margin control. Set-head-width lists have their right 835 * margins shortened. The body for these lists has the offset 836 * necessarily lengthened. Everybody gets the offset. 837 */ 838 839 p->tcol->offset += offset; 840 841 switch (type) { 842 case LIST_bullet: 843 case LIST_dash: 844 case LIST_enum: 845 case LIST_hyphen: 846 case LIST_hang: 847 case LIST_tag: 848 if (n->type == ROFFT_HEAD) 849 p->tcol->rmargin = p->tcol->offset + width; 850 else 851 p->tcol->offset += width; 852 break; 853 case LIST_column: 854 assert(width); 855 p->tcol->rmargin = p->tcol->offset + width; 856 /* 857 * XXX - this behaviour is not documented: the 858 * right-most column is filled to the right margin. 859 */ 860 if (n->type == ROFFT_HEAD) 861 break; 862 if (n->next == NULL && p->tcol->rmargin < p->maxrmargin) 863 p->tcol->rmargin = p->maxrmargin; 864 break; 865 default: 866 break; 867 } 868 869 /* 870 * The dash, hyphen, bullet and enum lists all have a special 871 * HEAD character (temporarily bold, in some cases). 872 */ 873 874 if (n->type == ROFFT_HEAD) 875 switch (type) { 876 case LIST_bullet: 877 term_fontpush(p, TERMFONT_BOLD); 878 term_word(p, "\\[bu]"); 879 term_fontpop(p); 880 break; 881 case LIST_dash: 882 case LIST_hyphen: 883 term_fontpush(p, TERMFONT_BOLD); 884 term_word(p, "-"); 885 term_fontpop(p); 886 break; 887 case LIST_enum: 888 (pair->ppair->ppair->count)++; 889 (void)snprintf(buf, sizeof(buf), "%d.", 890 pair->ppair->ppair->count); 891 term_word(p, buf); 892 break; 893 default: 894 break; 895 } 896 897 /* 898 * If we're not going to process our children, indicate so here. 899 */ 900 901 switch (type) { 902 case LIST_bullet: 903 case LIST_item: 904 case LIST_dash: 905 case LIST_hyphen: 906 case LIST_enum: 907 if (n->type == ROFFT_HEAD) 908 return 0; 909 break; 910 case LIST_column: 911 if (n->type == ROFFT_HEAD) 912 return 0; 913 p->minbl = 0; 914 break; 915 default: 916 break; 917 } 918 919 return 1; 920 } 921 922 static void 923 termp_it_post(DECL_ARGS) 924 { 925 enum mdoc_list type; 926 927 if (n->type == ROFFT_BLOCK) 928 return; 929 930 type = n->parent->parent->parent->norm->Bl.type; 931 932 switch (type) { 933 case LIST_item: 934 case LIST_diag: 935 case LIST_inset: 936 if (n->type == ROFFT_BODY) 937 term_newln(p); 938 break; 939 case LIST_column: 940 if (n->type == ROFFT_BODY) 941 term_flushln(p); 942 break; 943 default: 944 term_newln(p); 945 break; 946 } 947 948 /* 949 * Now that our output is flushed, we can reset our tags. Since 950 * only `It' sets these flags, we're free to assume that nobody 951 * has munged them in the meanwhile. 952 */ 953 954 p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP | TERMP_BRIND | TERMP_HANG); 955 p->trailspace = 0; 956 } 957 958 static int 959 termp_nm_pre(DECL_ARGS) 960 { 961 const char *cp; 962 963 if (n->type == ROFFT_BLOCK) { 964 p->flags |= TERMP_PREKEEP; 965 return 1; 966 } 967 968 if (n->type == ROFFT_BODY) { 969 if (n->child == NULL) 970 return 0; 971 p->flags |= TERMP_NOSPACE; 972 cp = NULL; 973 if (n->prev->child != NULL) 974 cp = n->prev->child->string; 975 if (cp == NULL) 976 cp = meta->name; 977 if (cp == NULL) 978 p->tcol->offset += term_len(p, 6); 979 else 980 p->tcol->offset += term_len(p, 1) + 981 term_strlen(p, cp); 982 return 1; 983 } 984 985 if (n->child == NULL) 986 return 0; 987 988 if (n->type == ROFFT_HEAD) 989 synopsis_pre(p, n->parent); 990 991 if (n->type == ROFFT_HEAD && 992 n->next != NULL && n->next->child != NULL) { 993 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK | TERMP_BRIND; 994 p->trailspace = 1; 995 p->tcol->rmargin = p->tcol->offset + term_len(p, 1); 996 if (n->child == NULL) 997 p->tcol->rmargin += term_strlen(p, meta->name); 998 else if (n->child->type == ROFFT_TEXT) { 999 p->tcol->rmargin += term_strlen(p, n->child->string); 1000 if (n->child->next != NULL) 1001 p->flags |= TERMP_HANG; 1002 } else { 1003 p->tcol->rmargin += term_len(p, 5); 1004 p->flags |= TERMP_HANG; 1005 } 1006 } 1007 return termp_bold_pre(p, pair, meta, n); 1008 } 1009 1010 static void 1011 termp_nm_post(DECL_ARGS) 1012 { 1013 switch (n->type) { 1014 case ROFFT_BLOCK: 1015 p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); 1016 break; 1017 case ROFFT_HEAD: 1018 if (n->next == NULL || n->next->child == NULL) 1019 break; 1020 term_flushln(p); 1021 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG); 1022 p->trailspace = 0; 1023 break; 1024 case ROFFT_BODY: 1025 if (n->child != NULL) 1026 term_flushln(p); 1027 break; 1028 default: 1029 break; 1030 } 1031 } 1032 1033 static int 1034 termp_fl_pre(DECL_ARGS) 1035 { 1036 struct roff_node *nn; 1037 1038 term_fontpush(p, TERMFONT_BOLD); 1039 term_word(p, "\\-"); 1040 1041 if (n->child != NULL || 1042 ((nn = roff_node_next(n)) != NULL && 1043 nn->type != ROFFT_TEXT && 1044 (nn->flags & NODE_LINE) == 0)) 1045 p->flags |= TERMP_NOSPACE; 1046 1047 return 1; 1048 } 1049 1050 static int 1051 termp__a_pre(DECL_ARGS) 1052 { 1053 struct roff_node *nn; 1054 1055 if ((nn = roff_node_prev(n)) != NULL && nn->tok == MDOC__A && 1056 ((nn = roff_node_next(n)) == NULL || nn->tok != MDOC__A)) 1057 term_word(p, "and"); 1058 1059 return 1; 1060 } 1061 1062 static int 1063 termp_an_pre(DECL_ARGS) 1064 { 1065 1066 if (n->norm->An.auth == AUTH_split) { 1067 p->flags &= ~TERMP_NOSPLIT; 1068 p->flags |= TERMP_SPLIT; 1069 return 0; 1070 } 1071 if (n->norm->An.auth == AUTH_nosplit) { 1072 p->flags &= ~TERMP_SPLIT; 1073 p->flags |= TERMP_NOSPLIT; 1074 return 0; 1075 } 1076 1077 if (p->flags & TERMP_SPLIT) 1078 term_newln(p); 1079 1080 if (n->sec == SEC_AUTHORS && ! (p->flags & TERMP_NOSPLIT)) 1081 p->flags |= TERMP_SPLIT; 1082 1083 return 1; 1084 } 1085 1086 static int 1087 termp_ns_pre(DECL_ARGS) 1088 { 1089 1090 if ( ! (NODE_LINE & n->flags)) 1091 p->flags |= TERMP_NOSPACE; 1092 return 1; 1093 } 1094 1095 static int 1096 termp_rs_pre(DECL_ARGS) 1097 { 1098 if (SEC_SEE_ALSO != n->sec) 1099 return 1; 1100 if (n->type == ROFFT_BLOCK && roff_node_prev(n) != NULL) 1101 term_vspace(p); 1102 return 1; 1103 } 1104 1105 static int 1106 termp_ex_pre(DECL_ARGS) 1107 { 1108 term_newln(p); 1109 return 1; 1110 } 1111 1112 static int 1113 termp_nd_pre(DECL_ARGS) 1114 { 1115 if (n->type == ROFFT_BODY) 1116 term_word(p, "\\(en"); 1117 return 1; 1118 } 1119 1120 static int 1121 termp_bl_pre(DECL_ARGS) 1122 { 1123 switch (n->type) { 1124 case ROFFT_BLOCK: 1125 term_newln(p); 1126 return 1; 1127 case ROFFT_HEAD: 1128 return 0; 1129 default: 1130 return 1; 1131 } 1132 } 1133 1134 static void 1135 termp_bl_post(DECL_ARGS) 1136 { 1137 if (n->type != ROFFT_BLOCK) 1138 return; 1139 term_newln(p); 1140 if (n->tok != MDOC_Bl || n->norm->Bl.type != LIST_column) 1141 return; 1142 term_tab_set(p, NULL); 1143 term_tab_set(p, "T"); 1144 term_tab_set(p, ".5i"); 1145 } 1146 1147 static int 1148 termp_xr_pre(DECL_ARGS) 1149 { 1150 if (NULL == (n = n->child)) 1151 return 0; 1152 1153 assert(n->type == ROFFT_TEXT); 1154 term_word(p, n->string); 1155 1156 if (NULL == (n = n->next)) 1157 return 0; 1158 1159 p->flags |= TERMP_NOSPACE; 1160 term_word(p, "("); 1161 p->flags |= TERMP_NOSPACE; 1162 1163 assert(n->type == ROFFT_TEXT); 1164 term_word(p, n->string); 1165 1166 p->flags |= TERMP_NOSPACE; 1167 term_word(p, ")"); 1168 1169 return 0; 1170 } 1171 1172 /* 1173 * This decides how to assert whitespace before any of the SYNOPSIS set 1174 * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain 1175 * macro combos). 1176 */ 1177 static void 1178 synopsis_pre(struct termp *p, struct roff_node *n) 1179 { 1180 struct roff_node *np; 1181 1182 if ((n->flags & NODE_SYNPRETTY) == 0 || 1183 (np = roff_node_prev(n)) == NULL) 1184 return; 1185 1186 /* 1187 * If we're the second in a pair of like elements, emit our 1188 * newline and return. UNLESS we're `Fo', `Fn', `Fn', in which 1189 * case we soldier on. 1190 */ 1191 if (np->tok == n->tok && 1192 MDOC_Ft != n->tok && 1193 MDOC_Fo != n->tok && 1194 MDOC_Fn != n->tok) { 1195 term_newln(p); 1196 return; 1197 } 1198 1199 /* 1200 * If we're one of the SYNOPSIS set and non-like pair-wise after 1201 * another (or Fn/Fo, which we've let slip through) then assert 1202 * vertical space, else only newline and move on. 1203 */ 1204 switch (np->tok) { 1205 case MDOC_Fd: 1206 case MDOC_Fn: 1207 case MDOC_Fo: 1208 case MDOC_In: 1209 case MDOC_Vt: 1210 term_vspace(p); 1211 break; 1212 case MDOC_Ft: 1213 if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) { 1214 term_vspace(p); 1215 break; 1216 } 1217 /* FALLTHROUGH */ 1218 default: 1219 term_newln(p); 1220 break; 1221 } 1222 } 1223 1224 static int 1225 termp_vt_pre(DECL_ARGS) 1226 { 1227 switch (n->type) { 1228 case ROFFT_ELEM: 1229 return termp_ft_pre(p, pair, meta, n); 1230 case ROFFT_BLOCK: 1231 synopsis_pre(p, n); 1232 return 1; 1233 case ROFFT_HEAD: 1234 return 0; 1235 default: 1236 return termp_under_pre(p, pair, meta, n); 1237 } 1238 } 1239 1240 static int 1241 termp_bold_pre(DECL_ARGS) 1242 { 1243 term_fontpush(p, TERMFONT_BOLD); 1244 return 1; 1245 } 1246 1247 static int 1248 termp_fd_pre(DECL_ARGS) 1249 { 1250 synopsis_pre(p, n); 1251 return termp_bold_pre(p, pair, meta, n); 1252 } 1253 1254 static void 1255 termp_fd_post(DECL_ARGS) 1256 { 1257 term_newln(p); 1258 } 1259 1260 static int 1261 termp_sh_pre(DECL_ARGS) 1262 { 1263 struct roff_node *np; 1264 1265 switch (n->type) { 1266 case ROFFT_BLOCK: 1267 /* 1268 * Vertical space before sections, except 1269 * when the previous section was empty. 1270 */ 1271 if ((np = roff_node_prev(n)) == NULL || 1272 np->tok != MDOC_Sh || 1273 (np->body != NULL && np->body->child != NULL)) 1274 term_vspace(p); 1275 break; 1276 case ROFFT_HEAD: 1277 return termp_bold_pre(p, pair, meta, n); 1278 case ROFFT_BODY: 1279 p->tcol->offset = term_len(p, p->defindent); 1280 term_tab_set(p, NULL); 1281 term_tab_set(p, "T"); 1282 term_tab_set(p, ".5i"); 1283 if (n->sec == SEC_AUTHORS) 1284 p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT); 1285 break; 1286 default: 1287 break; 1288 } 1289 return 1; 1290 } 1291 1292 static void 1293 termp_sh_post(DECL_ARGS) 1294 { 1295 switch (n->type) { 1296 case ROFFT_HEAD: 1297 term_newln(p); 1298 break; 1299 case ROFFT_BODY: 1300 term_newln(p); 1301 p->tcol->offset = 0; 1302 break; 1303 default: 1304 break; 1305 } 1306 } 1307 1308 static void 1309 termp_lb_post(DECL_ARGS) 1310 { 1311 if (n->sec == SEC_LIBRARY && n->flags & NODE_LINE) 1312 term_newln(p); 1313 } 1314 1315 static int 1316 termp_d1_pre(DECL_ARGS) 1317 { 1318 if (n->type != ROFFT_BLOCK) 1319 return 1; 1320 term_newln(p); 1321 p->tcol->offset += term_len(p, p->defindent + 1); 1322 term_tab_set(p, NULL); 1323 term_tab_set(p, "T"); 1324 term_tab_set(p, ".5i"); 1325 return 1; 1326 } 1327 1328 static int 1329 termp_ft_pre(DECL_ARGS) 1330 { 1331 synopsis_pre(p, n); 1332 return termp_under_pre(p, pair, meta, n); 1333 } 1334 1335 static int 1336 termp_fn_pre(DECL_ARGS) 1337 { 1338 size_t rmargin = 0; 1339 int pretty; 1340 1341 synopsis_pre(p, n); 1342 pretty = n->flags & NODE_SYNPRETTY; 1343 if ((n = n->child) == NULL) 1344 return 0; 1345 1346 if (pretty) { 1347 rmargin = p->tcol->rmargin; 1348 p->tcol->rmargin = p->tcol->offset + term_len(p, 4); 1349 p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG; 1350 } 1351 1352 assert(n->type == ROFFT_TEXT); 1353 term_fontpush(p, TERMFONT_BOLD); 1354 term_word(p, n->string); 1355 term_fontpop(p); 1356 1357 if (pretty) { 1358 term_flushln(p); 1359 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG); 1360 p->flags |= TERMP_NOPAD; 1361 p->tcol->offset = p->tcol->rmargin; 1362 p->tcol->rmargin = rmargin; 1363 } 1364 1365 p->flags |= TERMP_NOSPACE; 1366 term_word(p, "("); 1367 p->flags |= TERMP_NOSPACE; 1368 1369 for (n = n->next; n; n = n->next) { 1370 assert(n->type == ROFFT_TEXT); 1371 term_fontpush(p, TERMFONT_UNDER); 1372 if (pretty) 1373 p->flags |= TERMP_NBRWORD; 1374 term_word(p, n->string); 1375 term_fontpop(p); 1376 1377 if (n->next) { 1378 p->flags |= TERMP_NOSPACE; 1379 term_word(p, ","); 1380 } 1381 } 1382 1383 p->flags |= TERMP_NOSPACE; 1384 term_word(p, ")"); 1385 1386 if (pretty) { 1387 p->flags |= TERMP_NOSPACE; 1388 term_word(p, ";"); 1389 term_flushln(p); 1390 } 1391 return 0; 1392 } 1393 1394 static int 1395 termp_fa_pre(DECL_ARGS) 1396 { 1397 const struct roff_node *nn; 1398 1399 if (n->parent->tok != MDOC_Fo) 1400 return termp_under_pre(p, pair, meta, n); 1401 1402 for (nn = n->child; nn != NULL; nn = nn->next) { 1403 term_fontpush(p, TERMFONT_UNDER); 1404 p->flags |= TERMP_NBRWORD; 1405 term_word(p, nn->string); 1406 term_fontpop(p); 1407 if (nn->next != NULL) { 1408 p->flags |= TERMP_NOSPACE; 1409 term_word(p, ","); 1410 } 1411 } 1412 if (n->child != NULL && 1413 (nn = roff_node_next(n)) != NULL && 1414 nn->tok == MDOC_Fa) { 1415 p->flags |= TERMP_NOSPACE; 1416 term_word(p, ","); 1417 } 1418 return 0; 1419 } 1420 1421 static int 1422 termp_bd_pre(DECL_ARGS) 1423 { 1424 int offset; 1425 1426 if (n->type == ROFFT_BLOCK) { 1427 print_bvspace(p, n, n); 1428 return 1; 1429 } else if (n->type == ROFFT_HEAD) 1430 return 0; 1431 1432 /* Handle the -offset argument. */ 1433 1434 if (n->norm->Bd.offs == NULL || 1435 ! strcmp(n->norm->Bd.offs, "left")) 1436 /* nothing */; 1437 else if ( ! strcmp(n->norm->Bd.offs, "indent")) 1438 p->tcol->offset += term_len(p, p->defindent + 1); 1439 else if ( ! strcmp(n->norm->Bd.offs, "indent-two")) 1440 p->tcol->offset += term_len(p, (p->defindent + 1) * 2); 1441 else { 1442 offset = a2width(p, n->norm->Bd.offs); 1443 if (offset < 0 && (size_t)(-offset) > p->tcol->offset) 1444 p->tcol->offset = 0; 1445 else if (offset < SHRT_MAX) 1446 p->tcol->offset += offset; 1447 } 1448 1449 switch (n->norm->Bd.type) { 1450 case DISP_literal: 1451 term_tab_set(p, NULL); 1452 term_tab_set(p, "T"); 1453 term_tab_set(p, "8n"); 1454 break; 1455 case DISP_centered: 1456 p->flags |= TERMP_CENTER; 1457 break; 1458 default: 1459 break; 1460 } 1461 return 1; 1462 } 1463 1464 static void 1465 termp_bd_post(DECL_ARGS) 1466 { 1467 if (n->type != ROFFT_BODY) 1468 return; 1469 if (n->norm->Bd.type == DISP_unfilled || 1470 n->norm->Bd.type == DISP_literal) 1471 p->flags |= TERMP_BRNEVER; 1472 p->flags |= TERMP_NOSPACE; 1473 term_newln(p); 1474 p->flags &= ~TERMP_BRNEVER; 1475 if (n->norm->Bd.type == DISP_centered) 1476 p->flags &= ~TERMP_CENTER; 1477 } 1478 1479 static int 1480 termp_xx_pre(DECL_ARGS) 1481 { 1482 if ((n->aux = p->flags & TERMP_PREKEEP) == 0) 1483 p->flags |= TERMP_PREKEEP; 1484 return 1; 1485 } 1486 1487 static void 1488 termp_xx_post(DECL_ARGS) 1489 { 1490 if (n->aux == 0) 1491 p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); 1492 } 1493 1494 static void 1495 termp_pf_post(DECL_ARGS) 1496 { 1497 if (n->next != NULL && (n->next->flags & NODE_LINE) == 0) 1498 p->flags |= TERMP_NOSPACE; 1499 } 1500 1501 static int 1502 termp_ss_pre(DECL_ARGS) 1503 { 1504 switch (n->type) { 1505 case ROFFT_BLOCK: 1506 if (roff_node_prev(n) == NULL) 1507 term_newln(p); 1508 else 1509 term_vspace(p); 1510 break; 1511 case ROFFT_HEAD: 1512 p->tcol->offset = term_len(p, (p->defindent+1)/2); 1513 return termp_bold_pre(p, pair, meta, n); 1514 case ROFFT_BODY: 1515 p->tcol->offset = term_len(p, p->defindent); 1516 term_tab_set(p, NULL); 1517 term_tab_set(p, "T"); 1518 term_tab_set(p, ".5i"); 1519 break; 1520 default: 1521 break; 1522 } 1523 return 1; 1524 } 1525 1526 static void 1527 termp_ss_post(DECL_ARGS) 1528 { 1529 if (n->type == ROFFT_HEAD || n->type == ROFFT_BODY) 1530 term_newln(p); 1531 } 1532 1533 static int 1534 termp_in_pre(DECL_ARGS) 1535 { 1536 synopsis_pre(p, n); 1537 if (n->flags & NODE_SYNPRETTY && n->flags & NODE_LINE) { 1538 term_fontpush(p, TERMFONT_BOLD); 1539 term_word(p, "#include"); 1540 term_word(p, "<"); 1541 } else { 1542 term_word(p, "<"); 1543 term_fontpush(p, TERMFONT_UNDER); 1544 } 1545 p->flags |= TERMP_NOSPACE; 1546 return 1; 1547 } 1548 1549 static void 1550 termp_in_post(DECL_ARGS) 1551 { 1552 if (n->flags & NODE_SYNPRETTY) 1553 term_fontpush(p, TERMFONT_BOLD); 1554 p->flags |= TERMP_NOSPACE; 1555 term_word(p, ">"); 1556 if (n->flags & NODE_SYNPRETTY) 1557 term_fontpop(p); 1558 } 1559 1560 static int 1561 termp_pp_pre(DECL_ARGS) 1562 { 1563 term_vspace(p); 1564 if (n->flags & NODE_ID) 1565 term_tag_write(n, p->line); 1566 return 0; 1567 } 1568 1569 static int 1570 termp_skip_pre(DECL_ARGS) 1571 { 1572 return 0; 1573 } 1574 1575 static int 1576 termp_quote_pre(DECL_ARGS) 1577 { 1578 if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM) 1579 return 1; 1580 1581 switch (n->tok) { 1582 case MDOC_Ao: 1583 case MDOC_Aq: 1584 term_word(p, n->child != NULL && n->child->next == NULL && 1585 n->child->tok == MDOC_Mt ? "<" : "\\(la"); 1586 break; 1587 case MDOC_Bro: 1588 case MDOC_Brq: 1589 term_word(p, "{"); 1590 break; 1591 case MDOC_Oo: 1592 case MDOC_Op: 1593 case MDOC_Bo: 1594 case MDOC_Bq: 1595 term_word(p, "["); 1596 break; 1597 case MDOC__T: 1598 /* FALLTHROUGH */ 1599 case MDOC_Do: 1600 case MDOC_Dq: 1601 term_word(p, "\\(lq"); 1602 break; 1603 case MDOC_En: 1604 if (NULL == n->norm->Es || 1605 NULL == n->norm->Es->child) 1606 return 1; 1607 term_word(p, n->norm->Es->child->string); 1608 break; 1609 case MDOC_Po: 1610 case MDOC_Pq: 1611 term_word(p, "("); 1612 break; 1613 case MDOC_Qo: 1614 case MDOC_Qq: 1615 term_word(p, "\""); 1616 break; 1617 case MDOC_Ql: 1618 case MDOC_So: 1619 case MDOC_Sq: 1620 term_word(p, "\\(oq"); 1621 break; 1622 default: 1623 abort(); 1624 } 1625 1626 p->flags |= TERMP_NOSPACE; 1627 return 1; 1628 } 1629 1630 static void 1631 termp_quote_post(DECL_ARGS) 1632 { 1633 1634 if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM) 1635 return; 1636 1637 p->flags |= TERMP_NOSPACE; 1638 1639 switch (n->tok) { 1640 case MDOC_Ao: 1641 case MDOC_Aq: 1642 term_word(p, n->child != NULL && n->child->next == NULL && 1643 n->child->tok == MDOC_Mt ? ">" : "\\(ra"); 1644 break; 1645 case MDOC_Bro: 1646 case MDOC_Brq: 1647 term_word(p, "}"); 1648 break; 1649 case MDOC_Oo: 1650 case MDOC_Op: 1651 case MDOC_Bo: 1652 case MDOC_Bq: 1653 term_word(p, "]"); 1654 break; 1655 case MDOC__T: 1656 /* FALLTHROUGH */ 1657 case MDOC_Do: 1658 case MDOC_Dq: 1659 term_word(p, "\\(rq"); 1660 break; 1661 case MDOC_En: 1662 if (n->norm->Es == NULL || 1663 n->norm->Es->child == NULL || 1664 n->norm->Es->child->next == NULL) 1665 p->flags &= ~TERMP_NOSPACE; 1666 else 1667 term_word(p, n->norm->Es->child->next->string); 1668 break; 1669 case MDOC_Po: 1670 case MDOC_Pq: 1671 term_word(p, ")"); 1672 break; 1673 case MDOC_Qo: 1674 case MDOC_Qq: 1675 term_word(p, "\""); 1676 break; 1677 case MDOC_Ql: 1678 case MDOC_So: 1679 case MDOC_Sq: 1680 term_word(p, "\\(cq"); 1681 break; 1682 default: 1683 abort(); 1684 } 1685 } 1686 1687 static int 1688 termp_eo_pre(DECL_ARGS) 1689 { 1690 1691 if (n->type != ROFFT_BODY) 1692 return 1; 1693 1694 if (n->end == ENDBODY_NOT && 1695 n->parent->head->child == NULL && 1696 n->child != NULL && 1697 n->child->end != ENDBODY_NOT) 1698 term_word(p, "\\&"); 1699 else if (n->end != ENDBODY_NOT ? n->child != NULL : 1700 n->parent->head->child != NULL && (n->child != NULL || 1701 (n->parent->tail != NULL && n->parent->tail->child != NULL))) 1702 p->flags |= TERMP_NOSPACE; 1703 1704 return 1; 1705 } 1706 1707 static void 1708 termp_eo_post(DECL_ARGS) 1709 { 1710 int body, tail; 1711 1712 if (n->type != ROFFT_BODY) 1713 return; 1714 1715 if (n->end != ENDBODY_NOT) { 1716 p->flags &= ~TERMP_NOSPACE; 1717 return; 1718 } 1719 1720 body = n->child != NULL || n->parent->head->child != NULL; 1721 tail = n->parent->tail != NULL && n->parent->tail->child != NULL; 1722 1723 if (body && tail) 1724 p->flags |= TERMP_NOSPACE; 1725 else if ( ! (body || tail)) 1726 term_word(p, "\\&"); 1727 else if ( ! tail) 1728 p->flags &= ~TERMP_NOSPACE; 1729 } 1730 1731 static int 1732 termp_fo_pre(DECL_ARGS) 1733 { 1734 size_t rmargin; 1735 1736 switch (n->type) { 1737 case ROFFT_BLOCK: 1738 synopsis_pre(p, n); 1739 return 1; 1740 case ROFFT_BODY: 1741 rmargin = p->tcol->rmargin; 1742 if (n->flags & NODE_SYNPRETTY) { 1743 p->tcol->rmargin = p->tcol->offset + term_len(p, 4); 1744 p->flags |= TERMP_NOBREAK | TERMP_BRIND | 1745 TERMP_HANG; 1746 } 1747 p->flags |= TERMP_NOSPACE; 1748 term_word(p, "("); 1749 p->flags |= TERMP_NOSPACE; 1750 if (n->flags & NODE_SYNPRETTY) { 1751 term_flushln(p); 1752 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | 1753 TERMP_HANG); 1754 p->flags |= TERMP_NOPAD; 1755 p->tcol->offset = p->tcol->rmargin; 1756 p->tcol->rmargin = rmargin; 1757 } 1758 return 1; 1759 default: 1760 return termp_bold_pre(p, pair, meta, n); 1761 } 1762 } 1763 1764 static void 1765 termp_fo_post(DECL_ARGS) 1766 { 1767 if (n->type != ROFFT_BODY) 1768 return; 1769 1770 p->flags |= TERMP_NOSPACE; 1771 term_word(p, ")"); 1772 1773 if (n->flags & NODE_SYNPRETTY) { 1774 p->flags |= TERMP_NOSPACE; 1775 term_word(p, ";"); 1776 term_flushln(p); 1777 } 1778 } 1779 1780 static int 1781 termp_bf_pre(DECL_ARGS) 1782 { 1783 switch (n->type) { 1784 case ROFFT_HEAD: 1785 return 0; 1786 case ROFFT_BODY: 1787 break; 1788 default: 1789 return 1; 1790 } 1791 switch (n->norm->Bf.font) { 1792 case FONT_Em: 1793 return termp_under_pre(p, pair, meta, n); 1794 case FONT_Sy: 1795 return termp_bold_pre(p, pair, meta, n); 1796 default: 1797 return termp_li_pre(p, pair, meta, n); 1798 } 1799 } 1800 1801 static int 1802 termp_sm_pre(DECL_ARGS) 1803 { 1804 if (n->child == NULL) 1805 p->flags ^= TERMP_NONOSPACE; 1806 else if (strcmp(n->child->string, "on") == 0) 1807 p->flags &= ~TERMP_NONOSPACE; 1808 else 1809 p->flags |= TERMP_NONOSPACE; 1810 1811 if (p->col && ! (TERMP_NONOSPACE & p->flags)) 1812 p->flags &= ~TERMP_NOSPACE; 1813 1814 return 0; 1815 } 1816 1817 static int 1818 termp_ap_pre(DECL_ARGS) 1819 { 1820 p->flags |= TERMP_NOSPACE; 1821 term_word(p, "'"); 1822 p->flags |= TERMP_NOSPACE; 1823 return 1; 1824 } 1825 1826 static void 1827 termp____post(DECL_ARGS) 1828 { 1829 struct roff_node *nn; 1830 1831 /* 1832 * Handle lists of authors. In general, print each followed by 1833 * a comma. Don't print the comma if there are only two 1834 * authors. 1835 */ 1836 if (n->tok == MDOC__A && 1837 (nn = roff_node_next(n)) != NULL && nn->tok == MDOC__A && 1838 ((nn = roff_node_next(nn)) == NULL || nn->tok != MDOC__A) && 1839 ((nn = roff_node_prev(n)) == NULL || nn->tok != MDOC__A)) 1840 return; 1841 1842 /* TODO: %U. */ 1843 1844 if (n->parent == NULL || n->parent->tok != MDOC_Rs) 1845 return; 1846 1847 p->flags |= TERMP_NOSPACE; 1848 if (roff_node_next(n) == NULL) { 1849 term_word(p, "."); 1850 p->flags |= TERMP_SENTENCE; 1851 } else 1852 term_word(p, ","); 1853 } 1854 1855 static int 1856 termp_li_pre(DECL_ARGS) 1857 { 1858 term_fontpush(p, TERMFONT_NONE); 1859 return 1; 1860 } 1861 1862 static int 1863 termp_lk_pre(DECL_ARGS) 1864 { 1865 const struct roff_node *link, *descr, *punct; 1866 1867 if ((link = n->child) == NULL) 1868 return 0; 1869 1870 /* Find beginning of trailing punctuation. */ 1871 punct = n->last; 1872 while (punct != link && punct->flags & NODE_DELIMC) 1873 punct = punct->prev; 1874 punct = punct->next; 1875 1876 /* Link text. */ 1877 if ((descr = link->next) != NULL && descr != punct) { 1878 term_fontpush(p, TERMFONT_UNDER); 1879 while (descr != punct) { 1880 if (descr->flags & (NODE_DELIMC | NODE_DELIMO)) 1881 p->flags |= TERMP_NOSPACE; 1882 term_word(p, descr->string); 1883 descr = descr->next; 1884 } 1885 term_fontpop(p); 1886 p->flags |= TERMP_NOSPACE; 1887 term_word(p, ":"); 1888 } 1889 1890 /* Link target. */ 1891 term_fontpush(p, TERMFONT_BOLD); 1892 term_word(p, link->string); 1893 term_fontpop(p); 1894 1895 /* Trailing punctuation. */ 1896 while (punct != NULL) { 1897 p->flags |= TERMP_NOSPACE; 1898 term_word(p, punct->string); 1899 punct = punct->next; 1900 } 1901 return 0; 1902 } 1903 1904 static int 1905 termp_bk_pre(DECL_ARGS) 1906 { 1907 switch (n->type) { 1908 case ROFFT_BLOCK: 1909 break; 1910 case ROFFT_HEAD: 1911 return 0; 1912 case ROFFT_BODY: 1913 if (n->parent->args != NULL || n->prev->child == NULL) 1914 p->flags |= TERMP_PREKEEP; 1915 break; 1916 default: 1917 abort(); 1918 } 1919 return 1; 1920 } 1921 1922 static void 1923 termp_bk_post(DECL_ARGS) 1924 { 1925 if (n->type == ROFFT_BODY) 1926 p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); 1927 } 1928 1929 /* 1930 * If we are in an `Rs' and there is a journal present, 1931 * then quote us instead of underlining us (for disambiguation). 1932 */ 1933 static void 1934 termp__t_post(DECL_ARGS) 1935 { 1936 if (n->parent != NULL && n->parent->tok == MDOC_Rs && 1937 n->parent->norm->Rs.quote_T) 1938 termp_quote_post(p, pair, meta, n); 1939 termp____post(p, pair, meta, n); 1940 } 1941 1942 static int 1943 termp__t_pre(DECL_ARGS) 1944 { 1945 if (n->parent != NULL && n->parent->tok == MDOC_Rs && 1946 n->parent->norm->Rs.quote_T) 1947 return termp_quote_pre(p, pair, meta, n); 1948 else 1949 return termp_under_pre(p, pair, meta, n); 1950 } 1951 1952 static int 1953 termp_under_pre(DECL_ARGS) 1954 { 1955 term_fontpush(p, TERMFONT_UNDER); 1956 return 1; 1957 } 1958 1959 static int 1960 termp_abort_pre(DECL_ARGS) 1961 { 1962 abort(); 1963 } 1964