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