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