1 /* $Id: mdoc_term.c,v 1.387 2025/07/27 15:27:28 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2010,2012-2020,2022,2025 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; /* In basic units. */ 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 char *title; 445 size_t datelen, titlen; /* In basic units. */ 446 447 assert(meta->title != NULL); 448 datelen = term_strlen(p, meta->date); 449 if (meta->msec == NULL) 450 title = mandoc_strdup(meta->title); 451 else 452 mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec); 453 titlen = term_strlen(p, title); 454 455 term_fontrepl(p, TERMFONT_NONE); 456 term_vspace(p); 457 458 /* Bottom left corner: operating system. */ 459 460 p->tcol->offset = 0; 461 p->tcol->rmargin = p->maxrmargin > datelen ? 462 (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0; 463 p->trailspace = 1; 464 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; 465 466 term_word(p, meta->os); 467 term_flushln(p); 468 469 /* At the bottom in the middle: manual date. */ 470 471 p->tcol->offset = p->tcol->rmargin; 472 p->tcol->rmargin = p->maxrmargin > titlen ? 473 p->maxrmargin - titlen : 0; 474 p->flags |= TERMP_NOSPACE; 475 476 term_word(p, meta->date); 477 term_flushln(p); 478 479 /* Bottom right corner: manual title and section. */ 480 481 p->tcol->offset = p->tcol->rmargin; 482 p->tcol->rmargin = p->maxrmargin; 483 p->trailspace = 0; 484 p->flags &= ~TERMP_NOBREAK; 485 p->flags |= TERMP_NOSPACE; 486 487 term_word(p, title); 488 term_flushln(p); 489 490 p->tcol->offset = 0; 491 p->flags = 0; 492 free(title); 493 } 494 495 static void 496 print_mdoc_head(struct termp *p, const struct roff_meta *meta) 497 { 498 char *volume, *title; 499 size_t vollen, titlen; /* In basic units. */ 500 501 assert(meta->vol); 502 if (NULL == meta->arch) 503 volume = mandoc_strdup(meta->vol); 504 else 505 mandoc_asprintf(&volume, "%s (%s)", 506 meta->vol, meta->arch); 507 vollen = term_strlen(p, volume); 508 509 /* Top left corner: manual title and section. */ 510 511 if (NULL == meta->msec) 512 title = mandoc_strdup(meta->title); 513 else 514 mandoc_asprintf(&title, "%s(%s)", 515 meta->title, meta->msec); 516 titlen = term_strlen(p, title); 517 518 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; 519 p->trailspace = 1; 520 p->tcol->offset = 0; 521 p->tcol->rmargin = 522 titlen * 2 + term_len(p, 2) + vollen < p->maxrmargin ? 523 (p->maxrmargin - vollen + term_len(p, 1)) / 2 : 524 vollen < p->maxrmargin ? p->maxrmargin - vollen : 0; 525 526 term_word(p, title); 527 term_flushln(p); 528 529 /* At the top in the middle: manual volume. */ 530 531 p->flags |= TERMP_NOSPACE; 532 p->tcol->offset = p->tcol->rmargin; 533 p->tcol->rmargin = p->tcol->offset + vollen + titlen < 534 p->maxrmargin ? p->maxrmargin - titlen : p->maxrmargin; 535 536 term_word(p, volume); 537 term_flushln(p); 538 539 /* Top right corner: title and section, again. */ 540 541 p->flags &= ~TERMP_NOBREAK; 542 p->trailspace = 0; 543 if (p->tcol->rmargin + titlen <= p->maxrmargin) { 544 p->flags |= TERMP_NOSPACE; 545 p->tcol->offset = p->tcol->rmargin; 546 p->tcol->rmargin = p->maxrmargin; 547 term_word(p, title); 548 term_flushln(p); 549 } 550 551 p->flags &= ~TERMP_NOSPACE; 552 p->tcol->offset = 0; 553 p->tcol->rmargin = p->maxrmargin; 554 free(title); 555 free(volume); 556 } 557 558 /* 559 * Interpret the string v as a scaled width or, if the syntax is invalid, 560 * measure how much width it takes up when printed. In both cases, 561 * return the width in basic units. 562 */ 563 static int 564 a2width(const struct termp *p, const char *v) 565 { 566 struct roffsu su; 567 const char *end; 568 569 end = a2roffsu(v, &su, SCALE_MAX); 570 if (end == NULL || *end != '\0') { 571 su.unit = SCALE_BU; 572 su.scale = term_strlen(p, v); 573 } 574 return term_hspan(p, &su); 575 } 576 577 /* 578 * Determine how much space to print out before block elements of `It' 579 * (and thus `Bl') and `Bd'. And then go ahead and print that space, 580 * too. 581 */ 582 static void 583 print_bvspace(struct termp *p, struct roff_node *bl, struct roff_node *n) 584 { 585 struct roff_node *nn; 586 587 term_newln(p); 588 589 if ((bl->tok == MDOC_Bd && bl->norm->Bd.comp) || 590 (bl->tok == MDOC_Bl && bl->norm->Bl.comp)) 591 return; 592 593 /* Do not vspace directly after Ss/Sh. */ 594 595 nn = n; 596 while (roff_node_prev(nn) == NULL) { 597 do { 598 nn = nn->parent; 599 if (nn->type == ROFFT_ROOT) 600 return; 601 } while (nn->type != ROFFT_BLOCK); 602 if (nn->tok == MDOC_Sh || nn->tok == MDOC_Ss) 603 return; 604 if (nn->tok == MDOC_It && 605 nn->parent->parent->norm->Bl.type != LIST_item) 606 break; 607 } 608 609 /* 610 * No vertical space after: 611 * items in .Bl -column 612 * items without a body in .Bl -diag 613 */ 614 615 if (bl->tok != MDOC_Bl || 616 n->prev == NULL || n->prev->tok != MDOC_It || 617 (bl->norm->Bl.type != LIST_column && 618 (bl->norm->Bl.type != LIST_diag || 619 n->prev->body->child != NULL))) 620 term_vspace(p); 621 } 622 623 624 static int 625 termp_it_pre(DECL_ARGS) 626 { 627 struct roffsu su; 628 char buf[24]; 629 const struct roff_node *bl, *nn; 630 size_t ncols; /* Number of columns in .Bl -column. */ 631 size_t dcol; /* Column spacing in basic units. */ 632 int i; /* Zero-based column index. */ 633 int offset; /* Start of column in basic units. */ 634 int width; /* Column width in basic units. */ 635 enum mdoc_list type; 636 637 if (n->type == ROFFT_BLOCK) { 638 print_bvspace(p, n->parent->parent, n); 639 if (n->flags & NODE_ID) 640 term_tag_write(n, p->line); 641 return 1; 642 } 643 644 bl = n->parent->parent->parent; 645 type = bl->norm->Bl.type; 646 647 /* 648 * Defaults for specific list types. 649 */ 650 651 switch (type) { 652 case LIST_bullet: 653 case LIST_dash: 654 case LIST_hyphen: 655 case LIST_enum: 656 width = term_len(p, 2); 657 break; 658 case LIST_hang: 659 case LIST_tag: 660 width = term_len(p, 8); 661 break; 662 case LIST_column: 663 width = term_len(p, 10); 664 break; 665 default: 666 width = 0; 667 break; 668 } 669 offset = 0; 670 671 /* 672 * First calculate width and offset. This is pretty easy unless 673 * we're a -column list, in which case all prior columns must 674 * be accounted for. 675 */ 676 677 if (bl->norm->Bl.offs != NULL) { 678 offset = a2width(p, bl->norm->Bl.offs); 679 if (offset < 0 && (size_t)(-offset) > p->tcol->offset) 680 offset = -p->tcol->offset; 681 else if (offset > SHRT_MAX) 682 offset = 0; 683 } 684 685 switch (type) { 686 case LIST_column: 687 if (n->type == ROFFT_HEAD) 688 break; 689 690 /* 691 * Imitate groff's column handling: 692 * - For each earlier column, add its width. 693 * - For less than 5 columns, add four more blanks per 694 * column. 695 * - For exactly 5 columns, add three more blank per 696 * column. 697 * - For more than 5 columns, add only one column. 698 */ 699 ncols = bl->norm->Bl.ncols; 700 dcol = ncols < 5 ? term_len(p, 4) : 701 ncols == 5 ? term_len(p, 3) : term_len(p, 1); 702 703 /* 704 * Calculate the offset by applying all prior ROFFT_BODY, 705 * so we stop at the ROFFT_HEAD (nn->prev == NULL). 706 */ 707 708 for (i = 0, nn = n->prev; 709 nn->prev && i < (int)ncols; 710 nn = nn->prev, i++) { 711 su.unit = SCALE_BU; 712 su.scale = term_strlen(p, bl->norm->Bl.cols[i]); 713 offset += term_hspan(p, &su) + dcol; 714 } 715 716 /* 717 * When exceeding the declared number of columns, leave 718 * the remaining widths at 0. This will later be 719 * adjusted to the default width of 10, or, for the last 720 * column, stretched to the right margin. 721 */ 722 if (i >= (int)ncols) 723 break; 724 725 /* 726 * Use the declared column widths, extended as explained 727 * in the preceding paragraph. 728 */ 729 su.unit = SCALE_BU; 730 su.scale = term_strlen(p, bl->norm->Bl.cols[i]); 731 width = term_hspan(p, &su) + dcol; 732 break; 733 default: 734 if (NULL == bl->norm->Bl.width) 735 break; 736 737 /* 738 * Note: buffer the width by 2, which is groff's magic 739 * number for buffering single arguments. See the above 740 * handling for column for how this changes. 741 */ 742 width = a2width(p, bl->norm->Bl.width) + term_len(p, 2); 743 if (width < 0 && (size_t)(-width) > p->tcol->offset) 744 width = -p->tcol->offset; 745 else if (width > SHRT_MAX) 746 width = 0; 747 break; 748 } 749 750 /* 751 * Whitespace control. Inset bodies need an initial space, 752 * while diagonal bodies need two. 753 */ 754 755 p->flags |= TERMP_NOSPACE; 756 757 switch (type) { 758 case LIST_diag: 759 if (n->type == ROFFT_BODY) 760 term_word(p, "\\ \\ "); 761 break; 762 case LIST_inset: 763 if (n->type == ROFFT_BODY && n->parent->head->child != NULL) 764 term_word(p, "\\ "); 765 break; 766 default: 767 break; 768 } 769 770 p->flags |= TERMP_NOSPACE; 771 772 switch (type) { 773 case LIST_diag: 774 if (n->type == ROFFT_HEAD) 775 term_fontpush(p, TERMFONT_BOLD); 776 break; 777 default: 778 break; 779 } 780 781 /* 782 * Pad and break control. This is the tricky part. These flags 783 * are documented in term_flushln() in term.c. Note that we're 784 * going to unset all of these flags in termp_it_post() when we 785 * exit. 786 */ 787 788 switch (type) { 789 case LIST_enum: 790 case LIST_bullet: 791 case LIST_dash: 792 case LIST_hyphen: 793 if (n->type == ROFFT_HEAD) { 794 p->flags |= TERMP_NOBREAK | TERMP_HANG; 795 p->trailspace = 1; 796 } else if (width <= (int)term_len(p, 2)) 797 p->flags |= TERMP_NOPAD; 798 break; 799 case LIST_hang: 800 if (n->type != ROFFT_HEAD) 801 break; 802 p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG; 803 p->trailspace = 1; 804 break; 805 case LIST_tag: 806 if (n->type != ROFFT_HEAD) 807 break; 808 809 p->flags |= TERMP_NOBREAK | TERMP_BRTRSP | TERMP_BRIND; 810 p->trailspace = 2; 811 812 if (NULL == n->next || NULL == n->next->child) 813 p->flags |= TERMP_HANG; 814 break; 815 case LIST_column: 816 if (n->type == ROFFT_HEAD) 817 break; 818 819 if (NULL == n->next) { 820 p->flags &= ~TERMP_NOBREAK; 821 p->trailspace = 0; 822 } else { 823 p->flags |= TERMP_NOBREAK; 824 p->trailspace = 1; 825 } 826 827 break; 828 case LIST_diag: 829 if (n->type != ROFFT_HEAD) 830 break; 831 p->flags |= TERMP_NOBREAK | TERMP_BRIND; 832 p->trailspace = 1; 833 break; 834 default: 835 break; 836 } 837 838 /* 839 * Margin control. Set-head-width lists have their right 840 * margins shortened. The body for these lists has the offset 841 * necessarily lengthened. Everybody gets the offset. 842 */ 843 844 p->tcol->offset += offset; 845 846 switch (type) { 847 case LIST_bullet: 848 case LIST_dash: 849 case LIST_enum: 850 case LIST_hyphen: 851 case LIST_hang: 852 case LIST_tag: 853 if (n->type == ROFFT_HEAD) 854 p->tcol->rmargin = p->tcol->offset + width; 855 else 856 p->tcol->offset += width; 857 break; 858 case LIST_column: 859 assert(width); 860 p->tcol->rmargin = p->tcol->offset + width; 861 /* 862 * XXX - this behaviour is not documented: the 863 * right-most column is filled to the right margin. 864 */ 865 if (n->type == ROFFT_HEAD) 866 break; 867 if (n->next == NULL && p->tcol->rmargin < p->maxrmargin) 868 p->tcol->rmargin = p->maxrmargin; 869 break; 870 default: 871 break; 872 } 873 874 /* 875 * The dash, hyphen, bullet and enum lists all have a special 876 * HEAD character (temporarily bold, in some cases). 877 */ 878 879 if (n->type == ROFFT_HEAD) 880 switch (type) { 881 case LIST_bullet: 882 term_fontpush(p, TERMFONT_BOLD); 883 term_word(p, "\\[bu]"); 884 term_fontpop(p); 885 break; 886 case LIST_dash: 887 case LIST_hyphen: 888 term_fontpush(p, TERMFONT_BOLD); 889 term_word(p, "-"); 890 term_fontpop(p); 891 break; 892 case LIST_enum: 893 (pair->ppair->ppair->count)++; 894 (void)snprintf(buf, sizeof(buf), "%d.", 895 pair->ppair->ppair->count); 896 term_word(p, buf); 897 break; 898 default: 899 break; 900 } 901 902 /* 903 * If we're not going to process our children, indicate so here. 904 */ 905 906 switch (type) { 907 case LIST_bullet: 908 case LIST_item: 909 case LIST_dash: 910 case LIST_hyphen: 911 case LIST_enum: 912 if (n->type == ROFFT_HEAD) 913 return 0; 914 break; 915 case LIST_column: 916 if (n->type == ROFFT_HEAD) 917 return 0; 918 p->minbl = 0; 919 break; 920 default: 921 break; 922 } 923 924 return 1; 925 } 926 927 static void 928 termp_it_post(DECL_ARGS) 929 { 930 enum mdoc_list type; 931 932 if (n->type == ROFFT_BLOCK) 933 return; 934 935 type = n->parent->parent->parent->norm->Bl.type; 936 937 switch (type) { 938 case LIST_item: 939 case LIST_diag: 940 case LIST_inset: 941 if (n->type == ROFFT_BODY) 942 term_newln(p); 943 break; 944 case LIST_column: 945 if (n->type == ROFFT_BODY) 946 term_flushln(p); 947 break; 948 default: 949 term_newln(p); 950 break; 951 } 952 953 /* 954 * Now that our output is flushed, we can reset our tags. Since 955 * only `It' sets these flags, we're free to assume that nobody 956 * has munged them in the meanwhile. 957 */ 958 959 p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP | TERMP_BRIND | TERMP_HANG); 960 p->trailspace = 0; 961 } 962 963 static int 964 termp_nm_pre(DECL_ARGS) 965 { 966 const char *cp; 967 968 if (n->type == ROFFT_BLOCK) { 969 p->flags |= TERMP_PREKEEP; 970 return 1; 971 } 972 973 if (n->type == ROFFT_BODY) { 974 if (n->child == NULL) 975 return 0; 976 p->flags |= TERMP_NOSPACE; 977 cp = NULL; 978 if (n->prev->child != NULL) 979 cp = n->prev->child->string; 980 if (cp == NULL) 981 cp = meta->name; 982 if (cp == NULL) 983 p->tcol->offset += term_len(p, 6); 984 else 985 p->tcol->offset += term_len(p, 1) + 986 term_strlen(p, cp); 987 return 1; 988 } 989 990 if (n->child == NULL) 991 return 0; 992 993 if (n->type == ROFFT_HEAD) 994 synopsis_pre(p, n->parent); 995 996 if (n->type == ROFFT_HEAD && 997 n->next != NULL && n->next->child != NULL) { 998 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK | TERMP_BRIND; 999 p->trailspace = 1; 1000 p->tcol->rmargin = p->tcol->offset + term_len(p, 1); 1001 if (n->child == NULL) 1002 p->tcol->rmargin += term_strlen(p, meta->name); 1003 else if (n->child->type == ROFFT_TEXT) { 1004 p->tcol->rmargin += term_strlen(p, n->child->string); 1005 if (n->child->next != NULL) 1006 p->flags |= TERMP_HANG; 1007 } else { 1008 p->tcol->rmargin += term_len(p, 5); 1009 p->flags |= TERMP_HANG; 1010 } 1011 } 1012 return termp_bold_pre(p, pair, meta, n); 1013 } 1014 1015 static void 1016 termp_nm_post(DECL_ARGS) 1017 { 1018 switch (n->type) { 1019 case ROFFT_BLOCK: 1020 p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); 1021 break; 1022 case ROFFT_HEAD: 1023 if (n->next == NULL || n->next->child == NULL) 1024 break; 1025 term_flushln(p); 1026 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG); 1027 p->trailspace = 0; 1028 break; 1029 case ROFFT_BODY: 1030 if (n->child != NULL) 1031 term_flushln(p); 1032 break; 1033 default: 1034 break; 1035 } 1036 } 1037 1038 static int 1039 termp_fl_pre(DECL_ARGS) 1040 { 1041 struct roff_node *nn; 1042 1043 term_fontpush(p, TERMFONT_BOLD); 1044 term_word(p, "\\-"); 1045 1046 if (n->child != NULL || 1047 ((nn = roff_node_next(n)) != NULL && 1048 nn->type != ROFFT_TEXT && 1049 (nn->flags & NODE_LINE) == 0)) 1050 p->flags |= TERMP_NOSPACE; 1051 1052 return 1; 1053 } 1054 1055 static int 1056 termp__a_pre(DECL_ARGS) 1057 { 1058 struct roff_node *nn; 1059 1060 if ((nn = roff_node_prev(n)) != NULL && nn->tok == MDOC__A && 1061 ((nn = roff_node_next(n)) == NULL || nn->tok != MDOC__A)) 1062 term_word(p, "and"); 1063 1064 return 1; 1065 } 1066 1067 static int 1068 termp_an_pre(DECL_ARGS) 1069 { 1070 1071 if (n->norm->An.auth == AUTH_split) { 1072 p->flags &= ~TERMP_NOSPLIT; 1073 p->flags |= TERMP_SPLIT; 1074 return 0; 1075 } 1076 if (n->norm->An.auth == AUTH_nosplit) { 1077 p->flags &= ~TERMP_SPLIT; 1078 p->flags |= TERMP_NOSPLIT; 1079 return 0; 1080 } 1081 1082 if (p->flags & TERMP_SPLIT) 1083 term_newln(p); 1084 1085 if (n->sec == SEC_AUTHORS && ! (p->flags & TERMP_NOSPLIT)) 1086 p->flags |= TERMP_SPLIT; 1087 1088 return 1; 1089 } 1090 1091 static int 1092 termp_ns_pre(DECL_ARGS) 1093 { 1094 1095 if ( ! (NODE_LINE & n->flags)) 1096 p->flags |= TERMP_NOSPACE; 1097 return 1; 1098 } 1099 1100 static int 1101 termp_rs_pre(DECL_ARGS) 1102 { 1103 if (SEC_SEE_ALSO != n->sec) 1104 return 1; 1105 if (n->type == ROFFT_BLOCK && roff_node_prev(n) != NULL) 1106 term_vspace(p); 1107 return 1; 1108 } 1109 1110 static int 1111 termp_ex_pre(DECL_ARGS) 1112 { 1113 term_newln(p); 1114 return 1; 1115 } 1116 1117 static int 1118 termp_nd_pre(DECL_ARGS) 1119 { 1120 if (n->type == ROFFT_BODY) 1121 term_word(p, "\\(en"); 1122 return 1; 1123 } 1124 1125 static int 1126 termp_bl_pre(DECL_ARGS) 1127 { 1128 switch (n->type) { 1129 case ROFFT_BLOCK: 1130 term_newln(p); 1131 return 1; 1132 case ROFFT_HEAD: 1133 return 0; 1134 default: 1135 return 1; 1136 } 1137 } 1138 1139 static void 1140 termp_bl_post(DECL_ARGS) 1141 { 1142 if (n->type != ROFFT_BLOCK) 1143 return; 1144 term_newln(p); 1145 if (n->tok != MDOC_Bl || n->norm->Bl.type != LIST_column) 1146 return; 1147 term_tab_set(p, NULL); 1148 term_tab_set(p, "T"); 1149 term_tab_set(p, ".5i"); 1150 } 1151 1152 static int 1153 termp_xr_pre(DECL_ARGS) 1154 { 1155 if (NULL == (n = n->child)) 1156 return 0; 1157 1158 assert(n->type == ROFFT_TEXT); 1159 term_word(p, n->string); 1160 1161 if (NULL == (n = n->next)) 1162 return 0; 1163 1164 p->flags |= TERMP_NOSPACE; 1165 term_word(p, "("); 1166 p->flags |= TERMP_NOSPACE; 1167 1168 assert(n->type == ROFFT_TEXT); 1169 term_word(p, n->string); 1170 1171 p->flags |= TERMP_NOSPACE; 1172 term_word(p, ")"); 1173 1174 return 0; 1175 } 1176 1177 /* 1178 * This decides how to assert whitespace before any of the SYNOPSIS set 1179 * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain 1180 * macro combos). 1181 */ 1182 static void 1183 synopsis_pre(struct termp *p, struct roff_node *n) 1184 { 1185 struct roff_node *np; 1186 1187 if ((n->flags & NODE_SYNPRETTY) == 0 || 1188 (np = roff_node_prev(n)) == NULL) 1189 return; 1190 1191 /* 1192 * If we're the second in a pair of like elements, emit our 1193 * newline and return. UNLESS we're `Fo', `Fn', `Fn', in which 1194 * case we soldier on. 1195 */ 1196 if (np->tok == n->tok && 1197 MDOC_Ft != n->tok && 1198 MDOC_Fo != n->tok && 1199 MDOC_Fn != n->tok) { 1200 term_newln(p); 1201 return; 1202 } 1203 1204 /* 1205 * If we're one of the SYNOPSIS set and non-like pair-wise after 1206 * another (or Fn/Fo, which we've let slip through) then assert 1207 * vertical space, else only newline and move on. 1208 */ 1209 switch (np->tok) { 1210 case MDOC_Fd: 1211 case MDOC_Fn: 1212 case MDOC_Fo: 1213 case MDOC_In: 1214 case MDOC_Vt: 1215 term_vspace(p); 1216 break; 1217 case MDOC_Ft: 1218 if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) { 1219 term_vspace(p); 1220 break; 1221 } 1222 /* FALLTHROUGH */ 1223 default: 1224 term_newln(p); 1225 break; 1226 } 1227 } 1228 1229 static int 1230 termp_vt_pre(DECL_ARGS) 1231 { 1232 switch (n->type) { 1233 case ROFFT_ELEM: 1234 return termp_ft_pre(p, pair, meta, n); 1235 case ROFFT_BLOCK: 1236 synopsis_pre(p, n); 1237 return 1; 1238 case ROFFT_HEAD: 1239 return 0; 1240 default: 1241 return termp_under_pre(p, pair, meta, n); 1242 } 1243 } 1244 1245 static int 1246 termp_bold_pre(DECL_ARGS) 1247 { 1248 term_fontpush(p, TERMFONT_BOLD); 1249 return 1; 1250 } 1251 1252 static int 1253 termp_fd_pre(DECL_ARGS) 1254 { 1255 synopsis_pre(p, n); 1256 return termp_bold_pre(p, pair, meta, n); 1257 } 1258 1259 static void 1260 termp_fd_post(DECL_ARGS) 1261 { 1262 term_newln(p); 1263 } 1264 1265 static int 1266 termp_sh_pre(DECL_ARGS) 1267 { 1268 struct roff_node *np; 1269 1270 switch (n->type) { 1271 case ROFFT_BLOCK: 1272 /* 1273 * Vertical space before sections, except 1274 * when the previous section was empty. 1275 */ 1276 if ((np = roff_node_prev(n)) == NULL || 1277 np->tok != MDOC_Sh || 1278 (np->body != NULL && np->body->child != NULL)) 1279 term_vspace(p); 1280 break; 1281 case ROFFT_HEAD: 1282 p->fontibi = 1; 1283 return termp_bold_pre(p, pair, meta, n); 1284 case ROFFT_BODY: 1285 p->tcol->offset = term_len(p, p->defindent); 1286 term_tab_set(p, NULL); 1287 term_tab_set(p, "T"); 1288 term_tab_set(p, ".5i"); 1289 if (n->sec == SEC_AUTHORS) 1290 p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT); 1291 break; 1292 default: 1293 break; 1294 } 1295 return 1; 1296 } 1297 1298 static void 1299 termp_sh_post(DECL_ARGS) 1300 { 1301 switch (n->type) { 1302 case ROFFT_HEAD: 1303 p->fontibi = 0; 1304 term_newln(p); 1305 break; 1306 case ROFFT_BODY: 1307 term_newln(p); 1308 p->tcol->offset = 0; 1309 break; 1310 default: 1311 break; 1312 } 1313 } 1314 1315 static void 1316 termp_lb_post(DECL_ARGS) 1317 { 1318 if (n->sec == SEC_LIBRARY && n->flags & NODE_LINE) 1319 term_newln(p); 1320 } 1321 1322 static int 1323 termp_d1_pre(DECL_ARGS) 1324 { 1325 if (n->type != ROFFT_BLOCK) 1326 return 1; 1327 term_newln(p); 1328 p->tcol->offset += term_len(p, p->defindent + 1); 1329 term_tab_set(p, NULL); 1330 term_tab_set(p, "T"); 1331 term_tab_set(p, ".5i"); 1332 return 1; 1333 } 1334 1335 static int 1336 termp_ft_pre(DECL_ARGS) 1337 { 1338 synopsis_pre(p, n); 1339 return termp_under_pre(p, pair, meta, n); 1340 } 1341 1342 static int 1343 termp_fn_pre(DECL_ARGS) 1344 { 1345 size_t rmargin = 0; 1346 int pretty; 1347 1348 synopsis_pre(p, n); 1349 pretty = n->flags & NODE_SYNPRETTY; 1350 if ((n = n->child) == NULL) 1351 return 0; 1352 1353 if (pretty) { 1354 rmargin = p->tcol->rmargin; 1355 p->tcol->rmargin = p->tcol->offset + term_len(p, 4); 1356 p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG; 1357 } 1358 1359 assert(n->type == ROFFT_TEXT); 1360 term_fontpush(p, TERMFONT_BOLD); 1361 term_word(p, n->string); 1362 term_fontpop(p); 1363 1364 if (pretty) { 1365 term_flushln(p); 1366 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG); 1367 p->flags |= TERMP_NOPAD; 1368 p->tcol->offset = p->tcol->rmargin; 1369 p->tcol->rmargin = rmargin; 1370 } 1371 1372 p->flags |= TERMP_NOSPACE; 1373 term_word(p, "("); 1374 p->flags |= TERMP_NOSPACE; 1375 1376 for (n = n->next; n; n = n->next) { 1377 assert(n->type == ROFFT_TEXT); 1378 term_fontpush(p, TERMFONT_UNDER); 1379 if (pretty) 1380 p->flags |= TERMP_NBRWORD; 1381 term_word(p, n->string); 1382 term_fontpop(p); 1383 1384 if (n->next) { 1385 p->flags |= TERMP_NOSPACE; 1386 term_word(p, ","); 1387 } 1388 } 1389 1390 p->flags |= TERMP_NOSPACE; 1391 term_word(p, ")"); 1392 1393 if (pretty) { 1394 p->flags |= TERMP_NOSPACE; 1395 term_word(p, ";"); 1396 term_flushln(p); 1397 } 1398 return 0; 1399 } 1400 1401 static int 1402 termp_fa_pre(DECL_ARGS) 1403 { 1404 const struct roff_node *nn; 1405 1406 if (n->parent->tok != MDOC_Fo) 1407 return termp_under_pre(p, pair, meta, n); 1408 1409 for (nn = n->child; nn != NULL; nn = nn->next) { 1410 term_fontpush(p, TERMFONT_UNDER); 1411 p->flags |= TERMP_NBRWORD; 1412 term_word(p, nn->string); 1413 term_fontpop(p); 1414 if (nn->next != NULL) { 1415 p->flags |= TERMP_NOSPACE; 1416 term_word(p, ","); 1417 } 1418 } 1419 if (n->child != NULL && 1420 (nn = roff_node_next(n)) != NULL && 1421 nn->tok == MDOC_Fa) { 1422 p->flags |= TERMP_NOSPACE; 1423 term_word(p, ","); 1424 } 1425 return 0; 1426 } 1427 1428 static int 1429 termp_bd_pre(DECL_ARGS) 1430 { 1431 int offset; /* In basic units. */ 1432 1433 if (n->type == ROFFT_BLOCK) { 1434 print_bvspace(p, n, n); 1435 return 1; 1436 } else if (n->type == ROFFT_HEAD) 1437 return 0; 1438 1439 /* Handle the -offset argument. */ 1440 1441 if (n->norm->Bd.offs == NULL || 1442 ! strcmp(n->norm->Bd.offs, "left")) 1443 /* nothing */; 1444 else if ( ! strcmp(n->norm->Bd.offs, "indent")) 1445 p->tcol->offset += term_len(p, p->defindent + 1); 1446 else if ( ! strcmp(n->norm->Bd.offs, "indent-two")) 1447 p->tcol->offset += term_len(p, (p->defindent + 1) * 2); 1448 else { 1449 offset = a2width(p, n->norm->Bd.offs); 1450 if (offset < 0 && (size_t)(-offset) > p->tcol->offset) 1451 p->tcol->offset = 0; 1452 else if (offset < SHRT_MAX) 1453 p->tcol->offset += offset; 1454 } 1455 1456 switch (n->norm->Bd.type) { 1457 case DISP_literal: 1458 term_tab_set(p, NULL); 1459 term_tab_set(p, "T"); 1460 term_tab_set(p, "8n"); 1461 break; 1462 case DISP_centered: 1463 p->flags |= TERMP_CENTER; 1464 break; 1465 default: 1466 break; 1467 } 1468 return 1; 1469 } 1470 1471 static void 1472 termp_bd_post(DECL_ARGS) 1473 { 1474 if (n->type != ROFFT_BODY) 1475 return; 1476 if (n->norm->Bd.type == DISP_unfilled || 1477 n->norm->Bd.type == DISP_literal) 1478 p->flags |= TERMP_BRNEVER; 1479 p->flags |= TERMP_NOSPACE; 1480 term_newln(p); 1481 p->flags &= ~TERMP_BRNEVER; 1482 if (n->norm->Bd.type == DISP_centered) 1483 p->flags &= ~TERMP_CENTER; 1484 } 1485 1486 static int 1487 termp_xx_pre(DECL_ARGS) 1488 { 1489 if ((n->aux = p->flags & TERMP_PREKEEP) == 0) 1490 p->flags |= TERMP_PREKEEP; 1491 return 1; 1492 } 1493 1494 static void 1495 termp_xx_post(DECL_ARGS) 1496 { 1497 if (n->aux == 0) 1498 p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); 1499 } 1500 1501 static void 1502 termp_pf_post(DECL_ARGS) 1503 { 1504 if (n->next != NULL && (n->next->flags & NODE_LINE) == 0) 1505 p->flags |= TERMP_NOSPACE; 1506 } 1507 1508 static int 1509 termp_ss_pre(DECL_ARGS) 1510 { 1511 switch (n->type) { 1512 case ROFFT_BLOCK: 1513 if (roff_node_prev(n) == NULL) 1514 term_newln(p); 1515 else 1516 term_vspace(p); 1517 break; 1518 case ROFFT_HEAD: 1519 p->tcol->offset = term_len(p, p->defindent) / 2 + 1; 1520 p->fontibi = 1; 1521 return termp_bold_pre(p, pair, meta, n); 1522 case ROFFT_BODY: 1523 p->tcol->offset = term_len(p, p->defindent); 1524 term_tab_set(p, NULL); 1525 term_tab_set(p, "T"); 1526 term_tab_set(p, ".5i"); 1527 break; 1528 default: 1529 break; 1530 } 1531 return 1; 1532 } 1533 1534 static void 1535 termp_ss_post(DECL_ARGS) 1536 { 1537 switch (n->type) { 1538 case ROFFT_HEAD: 1539 p->fontibi = 0; 1540 /* FALLTHROUGH */ 1541 case ROFFT_BODY: 1542 term_newln(p); 1543 break; 1544 default: 1545 break; 1546 } 1547 } 1548 1549 static int 1550 termp_in_pre(DECL_ARGS) 1551 { 1552 synopsis_pre(p, n); 1553 if (n->flags & NODE_SYNPRETTY && n->flags & NODE_LINE) { 1554 term_fontpush(p, TERMFONT_BOLD); 1555 term_word(p, "#include"); 1556 term_word(p, "<"); 1557 } else { 1558 term_word(p, "<"); 1559 term_fontpush(p, TERMFONT_UNDER); 1560 } 1561 p->flags |= TERMP_NOSPACE; 1562 return 1; 1563 } 1564 1565 static void 1566 termp_in_post(DECL_ARGS) 1567 { 1568 if (n->flags & NODE_SYNPRETTY) 1569 term_fontpush(p, TERMFONT_BOLD); 1570 p->flags |= TERMP_NOSPACE; 1571 term_word(p, ">"); 1572 if (n->flags & NODE_SYNPRETTY) 1573 term_fontpop(p); 1574 } 1575 1576 static int 1577 termp_pp_pre(DECL_ARGS) 1578 { 1579 term_vspace(p); 1580 if (n->flags & NODE_ID) 1581 term_tag_write(n, p->line); 1582 return 0; 1583 } 1584 1585 static int 1586 termp_skip_pre(DECL_ARGS) 1587 { 1588 return 0; 1589 } 1590 1591 static int 1592 termp_quote_pre(DECL_ARGS) 1593 { 1594 if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM) 1595 return 1; 1596 1597 switch (n->tok) { 1598 case MDOC_Ao: 1599 case MDOC_Aq: 1600 term_word(p, n->child != NULL && n->child->next == NULL && 1601 n->child->tok == MDOC_Mt ? "<" : "\\(la"); 1602 break; 1603 case MDOC_Bro: 1604 case MDOC_Brq: 1605 term_word(p, "{"); 1606 break; 1607 case MDOC_Oo: 1608 case MDOC_Op: 1609 case MDOC_Bo: 1610 case MDOC_Bq: 1611 term_word(p, "["); 1612 break; 1613 case MDOC__T: 1614 /* FALLTHROUGH */ 1615 case MDOC_Do: 1616 case MDOC_Dq: 1617 term_word(p, "\\(lq"); 1618 break; 1619 case MDOC_En: 1620 if (NULL == n->norm->Es || 1621 NULL == n->norm->Es->child) 1622 return 1; 1623 term_word(p, n->norm->Es->child->string); 1624 break; 1625 case MDOC_Po: 1626 case MDOC_Pq: 1627 term_word(p, "("); 1628 break; 1629 case MDOC_Qo: 1630 case MDOC_Qq: 1631 term_word(p, "\""); 1632 break; 1633 case MDOC_Ql: 1634 case MDOC_So: 1635 case MDOC_Sq: 1636 term_word(p, "\\(oq"); 1637 break; 1638 default: 1639 abort(); 1640 } 1641 1642 p->flags |= TERMP_NOSPACE; 1643 return 1; 1644 } 1645 1646 static void 1647 termp_quote_post(DECL_ARGS) 1648 { 1649 1650 if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM) 1651 return; 1652 1653 p->flags |= TERMP_NOSPACE; 1654 1655 switch (n->tok) { 1656 case MDOC_Ao: 1657 case MDOC_Aq: 1658 term_word(p, n->child != NULL && n->child->next == NULL && 1659 n->child->tok == MDOC_Mt ? ">" : "\\(ra"); 1660 break; 1661 case MDOC_Bro: 1662 case MDOC_Brq: 1663 term_word(p, "}"); 1664 break; 1665 case MDOC_Oo: 1666 case MDOC_Op: 1667 case MDOC_Bo: 1668 case MDOC_Bq: 1669 term_word(p, "]"); 1670 break; 1671 case MDOC__T: 1672 /* FALLTHROUGH */ 1673 case MDOC_Do: 1674 case MDOC_Dq: 1675 term_word(p, "\\(rq"); 1676 break; 1677 case MDOC_En: 1678 if (n->norm->Es == NULL || 1679 n->norm->Es->child == NULL || 1680 n->norm->Es->child->next == NULL) 1681 p->flags &= ~TERMP_NOSPACE; 1682 else 1683 term_word(p, n->norm->Es->child->next->string); 1684 break; 1685 case MDOC_Po: 1686 case MDOC_Pq: 1687 term_word(p, ")"); 1688 break; 1689 case MDOC_Qo: 1690 case MDOC_Qq: 1691 term_word(p, "\""); 1692 break; 1693 case MDOC_Ql: 1694 case MDOC_So: 1695 case MDOC_Sq: 1696 term_word(p, "\\(cq"); 1697 break; 1698 default: 1699 abort(); 1700 } 1701 } 1702 1703 static int 1704 termp_eo_pre(DECL_ARGS) 1705 { 1706 1707 if (n->type != ROFFT_BODY) 1708 return 1; 1709 1710 if (n->end == ENDBODY_NOT && 1711 n->parent->head->child == NULL && 1712 n->child != NULL && 1713 n->child->end != ENDBODY_NOT) 1714 term_word(p, "\\&"); 1715 else if (n->end != ENDBODY_NOT ? n->child != NULL : 1716 n->parent->head->child != NULL && (n->child != NULL || 1717 (n->parent->tail != NULL && n->parent->tail->child != NULL))) 1718 p->flags |= TERMP_NOSPACE; 1719 1720 return 1; 1721 } 1722 1723 static void 1724 termp_eo_post(DECL_ARGS) 1725 { 1726 int body, tail; 1727 1728 if (n->type != ROFFT_BODY) 1729 return; 1730 1731 if (n->end != ENDBODY_NOT) { 1732 p->flags &= ~TERMP_NOSPACE; 1733 return; 1734 } 1735 1736 body = n->child != NULL || n->parent->head->child != NULL; 1737 tail = n->parent->tail != NULL && n->parent->tail->child != NULL; 1738 1739 if (body && tail) 1740 p->flags |= TERMP_NOSPACE; 1741 else if ( ! (body || tail)) 1742 term_word(p, "\\&"); 1743 else if ( ! tail) 1744 p->flags &= ~TERMP_NOSPACE; 1745 } 1746 1747 static int 1748 termp_fo_pre(DECL_ARGS) 1749 { 1750 size_t rmargin; 1751 1752 switch (n->type) { 1753 case ROFFT_BLOCK: 1754 synopsis_pre(p, n); 1755 return 1; 1756 case ROFFT_BODY: 1757 rmargin = p->tcol->rmargin; 1758 if (n->flags & NODE_SYNPRETTY) { 1759 p->tcol->rmargin = p->tcol->offset + term_len(p, 4); 1760 p->flags |= TERMP_NOBREAK | TERMP_BRIND | 1761 TERMP_HANG; 1762 } 1763 p->flags |= TERMP_NOSPACE; 1764 term_word(p, "("); 1765 p->flags |= TERMP_NOSPACE; 1766 if (n->flags & NODE_SYNPRETTY) { 1767 term_flushln(p); 1768 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | 1769 TERMP_HANG); 1770 p->flags |= TERMP_NOPAD; 1771 p->tcol->offset = p->tcol->rmargin; 1772 p->tcol->rmargin = rmargin; 1773 } 1774 return 1; 1775 default: 1776 return termp_bold_pre(p, pair, meta, n); 1777 } 1778 } 1779 1780 static void 1781 termp_fo_post(DECL_ARGS) 1782 { 1783 if (n->type != ROFFT_BODY) 1784 return; 1785 1786 p->flags |= TERMP_NOSPACE; 1787 term_word(p, ")"); 1788 1789 if (n->flags & NODE_SYNPRETTY) { 1790 p->flags |= TERMP_NOSPACE; 1791 term_word(p, ";"); 1792 term_flushln(p); 1793 } 1794 } 1795 1796 static int 1797 termp_bf_pre(DECL_ARGS) 1798 { 1799 switch (n->type) { 1800 case ROFFT_HEAD: 1801 return 0; 1802 case ROFFT_BODY: 1803 break; 1804 default: 1805 return 1; 1806 } 1807 switch (n->norm->Bf.font) { 1808 case FONT_Em: 1809 return termp_under_pre(p, pair, meta, n); 1810 case FONT_Sy: 1811 return termp_bold_pre(p, pair, meta, n); 1812 default: 1813 return termp_li_pre(p, pair, meta, n); 1814 } 1815 } 1816 1817 static int 1818 termp_sm_pre(DECL_ARGS) 1819 { 1820 if (n->child == NULL) 1821 p->flags ^= TERMP_NONOSPACE; 1822 else if (strcmp(n->child->string, "on") == 0) 1823 p->flags &= ~TERMP_NONOSPACE; 1824 else 1825 p->flags |= TERMP_NONOSPACE; 1826 1827 if (p->col && ! (TERMP_NONOSPACE & p->flags)) 1828 p->flags &= ~TERMP_NOSPACE; 1829 1830 return 0; 1831 } 1832 1833 static int 1834 termp_ap_pre(DECL_ARGS) 1835 { 1836 p->flags |= TERMP_NOSPACE; 1837 term_word(p, "'"); 1838 p->flags |= TERMP_NOSPACE; 1839 return 1; 1840 } 1841 1842 static void 1843 termp____post(DECL_ARGS) 1844 { 1845 struct roff_node *nn; 1846 1847 /* 1848 * Handle lists of authors. In general, print each followed by 1849 * a comma. Don't print the comma if there are only two 1850 * authors. 1851 */ 1852 if (n->tok == MDOC__A && 1853 (nn = roff_node_next(n)) != NULL && nn->tok == MDOC__A && 1854 ((nn = roff_node_next(nn)) == NULL || nn->tok != MDOC__A) && 1855 ((nn = roff_node_prev(n)) == NULL || nn->tok != MDOC__A)) 1856 return; 1857 1858 /* TODO: %U. */ 1859 1860 if (n->parent == NULL || n->parent->tok != MDOC_Rs) 1861 return; 1862 1863 p->flags |= TERMP_NOSPACE; 1864 if (roff_node_next(n) == NULL) { 1865 term_word(p, "."); 1866 p->flags |= TERMP_SENTENCE; 1867 } else 1868 term_word(p, ","); 1869 } 1870 1871 static int 1872 termp_li_pre(DECL_ARGS) 1873 { 1874 term_fontpush(p, TERMFONT_NONE); 1875 return 1; 1876 } 1877 1878 static int 1879 termp_lk_pre(DECL_ARGS) 1880 { 1881 const struct roff_node *link, *descr, *punct; 1882 1883 if ((link = n->child) == NULL) 1884 return 0; 1885 1886 /* Find beginning of trailing punctuation. */ 1887 punct = n->last; 1888 while (punct != link && punct->flags & NODE_DELIMC) 1889 punct = punct->prev; 1890 punct = punct->next; 1891 1892 /* Link text. */ 1893 if ((descr = link->next) != NULL && descr != punct) { 1894 term_fontpush(p, TERMFONT_UNDER); 1895 while (descr != punct) { 1896 if (descr->flags & (NODE_DELIMC | NODE_DELIMO)) 1897 p->flags |= TERMP_NOSPACE; 1898 term_word(p, descr->string); 1899 descr = descr->next; 1900 } 1901 term_fontpop(p); 1902 p->flags |= TERMP_NOSPACE; 1903 term_word(p, ":"); 1904 } 1905 1906 /* Link target. */ 1907 term_word(p, link->string); 1908 1909 /* Trailing punctuation. */ 1910 while (punct != NULL) { 1911 p->flags |= TERMP_NOSPACE; 1912 term_word(p, punct->string); 1913 punct = punct->next; 1914 } 1915 return 0; 1916 } 1917 1918 static int 1919 termp_bk_pre(DECL_ARGS) 1920 { 1921 switch (n->type) { 1922 case ROFFT_BLOCK: 1923 break; 1924 case ROFFT_HEAD: 1925 return 0; 1926 case ROFFT_BODY: 1927 if (n->parent->args != NULL || n->prev->child == NULL) 1928 p->flags |= TERMP_PREKEEP; 1929 break; 1930 default: 1931 abort(); 1932 } 1933 return 1; 1934 } 1935 1936 static void 1937 termp_bk_post(DECL_ARGS) 1938 { 1939 if (n->type == ROFFT_BODY) 1940 p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); 1941 } 1942 1943 /* 1944 * If we are in an `Rs' and there is a journal present, 1945 * then quote us instead of underlining us (for disambiguation). 1946 */ 1947 static void 1948 termp__t_post(DECL_ARGS) 1949 { 1950 if (n->parent != NULL && n->parent->tok == MDOC_Rs && 1951 n->parent->norm->Rs.quote_T) 1952 termp_quote_post(p, pair, meta, n); 1953 termp____post(p, pair, meta, n); 1954 } 1955 1956 static int 1957 termp__t_pre(DECL_ARGS) 1958 { 1959 if (n->parent != NULL && n->parent->tok == MDOC_Rs && 1960 n->parent->norm->Rs.quote_T) 1961 return termp_quote_pre(p, pair, meta, n); 1962 else 1963 return termp_under_pre(p, pair, meta, n); 1964 } 1965 1966 static int 1967 termp_under_pre(DECL_ARGS) 1968 { 1969 term_fontpush(p, TERMFONT_UNDER); 1970 return 1; 1971 } 1972 1973 static int 1974 termp_abort_pre(DECL_ARGS) 1975 { 1976 abort(); 1977 } 1978