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