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