root/usr/src/cmd/mandoc/mdoc_man.c
/*      $Id: mdoc_man.c,v 1.137 2021/07/04 15:38:26 schwarze Exp $ */
/*
 * Copyright (c) 2011-2021 Ingo Schwarze <schwarze@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include "config.h"

#include <sys/types.h>

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "mandoc_aux.h"
#include "mandoc.h"
#include "roff.h"
#include "mdoc.h"
#include "man.h"
#include "out.h"
#include "main.h"

#define DECL_ARGS const struct roff_meta *meta, struct roff_node *n

typedef int     (*int_fp)(DECL_ARGS);
typedef void    (*void_fp)(DECL_ARGS);

struct  mdoc_man_act {
        int_fp            cond; /* DON'T run actions */
        int_fp            pre; /* pre-node action */
        void_fp           post; /* post-node action */
        const char       *prefix; /* pre-node string constant */
        const char       *suffix; /* post-node string constant */
};

static  int       cond_body(DECL_ARGS);
static  int       cond_head(DECL_ARGS);
static  void      font_push(char);
static  void      font_pop(void);
static  int       man_strlen(const char *);
static  void      mid_it(void);
static  void      post__t(DECL_ARGS);
static  void      post_aq(DECL_ARGS);
static  void      post_bd(DECL_ARGS);
static  void      post_bf(DECL_ARGS);
static  void      post_bk(DECL_ARGS);
static  void      post_bl(DECL_ARGS);
static  void      post_dl(DECL_ARGS);
static  void      post_en(DECL_ARGS);
static  void      post_enc(DECL_ARGS);
static  void      post_eo(DECL_ARGS);
static  void      post_fa(DECL_ARGS);
static  void      post_fd(DECL_ARGS);
static  void      post_fl(DECL_ARGS);
static  void      post_fn(DECL_ARGS);
static  void      post_fo(DECL_ARGS);
static  void      post_font(DECL_ARGS);
static  void      post_in(DECL_ARGS);
static  void      post_it(DECL_ARGS);
static  void      post_lb(DECL_ARGS);
static  void      post_nm(DECL_ARGS);
static  void      post_percent(DECL_ARGS);
static  void      post_pf(DECL_ARGS);
static  void      post_sect(DECL_ARGS);
static  void      post_vt(DECL_ARGS);
static  int       pre__t(DECL_ARGS);
static  int       pre_abort(DECL_ARGS);
static  int       pre_an(DECL_ARGS);
static  int       pre_ap(DECL_ARGS);
static  int       pre_aq(DECL_ARGS);
static  int       pre_bd(DECL_ARGS);
static  int       pre_bf(DECL_ARGS);
static  int       pre_bk(DECL_ARGS);
static  int       pre_bl(DECL_ARGS);
static  void      pre_br(DECL_ARGS);
static  int       pre_dl(DECL_ARGS);
static  int       pre_en(DECL_ARGS);
static  int       pre_enc(DECL_ARGS);
static  int       pre_em(DECL_ARGS);
static  int       pre_skip(DECL_ARGS);
static  int       pre_eo(DECL_ARGS);
static  int       pre_ex(DECL_ARGS);
static  int       pre_fa(DECL_ARGS);
static  int       pre_fd(DECL_ARGS);
static  int       pre_fl(DECL_ARGS);
static  int       pre_fn(DECL_ARGS);
static  int       pre_fo(DECL_ARGS);
static  void      pre_ft(DECL_ARGS);
static  int       pre_Ft(DECL_ARGS);
static  int       pre_in(DECL_ARGS);
static  int       pre_it(DECL_ARGS);
static  int       pre_lk(DECL_ARGS);
static  int       pre_li(DECL_ARGS);
static  int       pre_nm(DECL_ARGS);
static  int       pre_no(DECL_ARGS);
static  void      pre_noarg(DECL_ARGS);
static  int       pre_ns(DECL_ARGS);
static  void      pre_onearg(DECL_ARGS);
static  int       pre_pp(DECL_ARGS);
static  int       pre_rs(DECL_ARGS);
static  int       pre_sm(DECL_ARGS);
static  void      pre_sp(DECL_ARGS);
static  int       pre_sect(DECL_ARGS);
static  int       pre_sy(DECL_ARGS);
static  void      pre_syn(struct roff_node *);
static  void      pre_ta(DECL_ARGS);
static  int       pre_vt(DECL_ARGS);
static  int       pre_xr(DECL_ARGS);
static  void      print_word(const char *);
static  void      print_line(const char *, int);
static  void      print_block(const char *, int);
static  void      print_offs(const char *, int);
static  void      print_width(const struct mdoc_bl *,
                        const struct roff_node *);
static  void      print_count(int *);
static  void      print_node(DECL_ARGS);

static const void_fp roff_man_acts[ROFF_MAX] = {
        pre_br,         /* br */
        pre_onearg,     /* ce */
        pre_noarg,      /* fi */
        pre_ft,         /* ft */
        pre_onearg,     /* ll */
        pre_onearg,     /* mc */
        pre_noarg,      /* nf */
        pre_onearg,     /* po */
        pre_onearg,     /* rj */
        pre_sp,         /* sp */
        pre_ta,         /* ta */
        pre_onearg,     /* ti */
};

static const struct mdoc_man_act mdoc_man_acts[MDOC_MAX - MDOC_Dd] = {
        { NULL, NULL, NULL, NULL, NULL }, /* Dd */
        { NULL, NULL, NULL, NULL, NULL }, /* Dt */
        { NULL, NULL, NULL, NULL, NULL }, /* Os */
        { NULL, pre_sect, post_sect, ".SH", NULL }, /* Sh */
        { NULL, pre_sect, post_sect, ".SS", NULL }, /* Ss */
        { NULL, pre_pp, NULL, NULL, NULL }, /* Pp */
        { cond_body, pre_dl, post_dl, NULL, NULL }, /* D1 */
        { cond_body, pre_dl, post_dl, NULL, NULL }, /* Dl */
        { cond_body, pre_bd, post_bd, NULL, NULL }, /* Bd */
        { NULL, NULL, NULL, NULL, NULL }, /* Ed */
        { cond_body, pre_bl, post_bl, NULL, NULL }, /* Bl */
        { NULL, NULL, NULL, NULL, NULL }, /* El */
        { NULL, pre_it, post_it, NULL, NULL }, /* It */
        { NULL, pre_em, post_font, NULL, NULL }, /* Ad */
        { NULL, pre_an, NULL, NULL, NULL }, /* An */
        { NULL, pre_ap, NULL, NULL, NULL }, /* Ap */
        { NULL, pre_em, post_font, NULL, NULL }, /* Ar */
        { NULL, pre_sy, post_font, NULL, NULL }, /* Cd */
        { NULL, pre_sy, post_font, NULL, NULL }, /* Cm */
        { NULL, pre_li, post_font, NULL, NULL }, /* Dv */
        { NULL, pre_li, post_font, NULL, NULL }, /* Er */
        { NULL, pre_li, post_font, NULL, NULL }, /* Ev */
        { NULL, pre_ex, NULL, NULL, NULL }, /* Ex */
        { NULL, pre_fa, post_fa, NULL, NULL }, /* Fa */
        { NULL, pre_fd, post_fd, NULL, NULL }, /* Fd */
        { NULL, pre_fl, post_fl, NULL, NULL }, /* Fl */
        { NULL, pre_fn, post_fn, NULL, NULL }, /* Fn */
        { NULL, pre_Ft, post_font, NULL, NULL }, /* Ft */
        { NULL, pre_sy, post_font, NULL, NULL }, /* Ic */
        { NULL, pre_in, post_in, NULL, NULL }, /* In */
        { NULL, pre_li, post_font, NULL, NULL }, /* Li */
        { cond_head, pre_enc, NULL, "\\- ", NULL }, /* Nd */
        { NULL, pre_nm, post_nm, NULL, NULL }, /* Nm */
        { cond_body, pre_enc, post_enc, "[", "]" }, /* Op */
        { NULL, pre_abort, NULL, NULL, NULL }, /* Ot */
        { NULL, pre_em, post_font, NULL, NULL }, /* Pa */
        { NULL, pre_ex, NULL, NULL, NULL }, /* Rv */
        { NULL, NULL, NULL, NULL, NULL }, /* St */
        { NULL, pre_em, post_font, NULL, NULL }, /* Va */
        { NULL, pre_vt, post_vt, NULL, NULL }, /* Vt */
        { NULL, pre_xr, NULL, NULL, NULL }, /* Xr */
        { NULL, NULL, post_percent, NULL, NULL }, /* %A */
        { NULL, pre_em, post_percent, NULL, NULL }, /* %B */
        { NULL, NULL, post_percent, NULL, NULL }, /* %D */
        { NULL, pre_em, post_percent, NULL, NULL }, /* %I */
        { NULL, pre_em, post_percent, NULL, NULL }, /* %J */
        { NULL, NULL, post_percent, NULL, NULL }, /* %N */
        { NULL, NULL, post_percent, NULL, NULL }, /* %O */
        { NULL, NULL, post_percent, NULL, NULL }, /* %P */
        { NULL, NULL, post_percent, NULL, NULL }, /* %R */
        { NULL, pre__t, post__t, NULL, NULL }, /* %T */
        { NULL, NULL, post_percent, NULL, NULL }, /* %V */
        { NULL, NULL, NULL, NULL, NULL }, /* Ac */
        { cond_body, pre_aq, post_aq, NULL, NULL }, /* Ao */
        { cond_body, pre_aq, post_aq, NULL, NULL }, /* Aq */
        { NULL, NULL, NULL, NULL, NULL }, /* At */
        { NULL, NULL, NULL, NULL, NULL }, /* Bc */
        { NULL, pre_bf, post_bf, NULL, NULL }, /* Bf */
        { cond_body, pre_enc, post_enc, "[", "]" }, /* Bo */
        { cond_body, pre_enc, post_enc, "[", "]" }, /* Bq */
        { NULL, pre_bk, post_bk, NULL, NULL }, /* Bsx */
        { NULL, pre_bk, post_bk, NULL, NULL }, /* Bx */
        { NULL, pre_skip, NULL, NULL, NULL }, /* Db */
        { NULL, NULL, NULL, NULL, NULL }, /* Dc */
        { cond_body, pre_enc, post_enc, "\\(lq", "\\(rq" }, /* Do */
        { cond_body, pre_enc, post_enc, "\\(lq", "\\(rq" }, /* Dq */
        { NULL, NULL, NULL, NULL, NULL }, /* Ec */
        { NULL, NULL, NULL, NULL, NULL }, /* Ef */
        { NULL, pre_em, post_font, NULL, NULL }, /* Em */
        { cond_body, pre_eo, post_eo, NULL, NULL }, /* Eo */
        { NULL, pre_bk, post_bk, NULL, NULL }, /* Fx */
        { NULL, pre_sy, post_font, NULL, NULL }, /* Ms */
        { NULL, pre_no, NULL, NULL, NULL }, /* No */
        { NULL, pre_ns, NULL, NULL, NULL }, /* Ns */
        { NULL, pre_bk, post_bk, NULL, NULL }, /* Nx */
        { NULL, pre_bk, post_bk, NULL, NULL }, /* Ox */
        { NULL, NULL, NULL, NULL, NULL }, /* Pc */
        { NULL, NULL, post_pf, NULL, NULL }, /* Pf */
        { cond_body, pre_enc, post_enc, "(", ")" }, /* Po */
        { cond_body, pre_enc, post_enc, "(", ")" }, /* Pq */
        { NULL, NULL, NULL, NULL, NULL }, /* Qc */
        { cond_body, pre_enc, post_enc, "\\(oq", "\\(cq" }, /* Ql */
        { cond_body, pre_enc, post_enc, "\"", "\"" }, /* Qo */
        { cond_body, pre_enc, post_enc, "\"", "\"" }, /* Qq */
        { NULL, NULL, NULL, NULL, NULL }, /* Re */
        { cond_body, pre_rs, NULL, NULL, NULL }, /* Rs */
        { NULL, NULL, NULL, NULL, NULL }, /* Sc */
        { cond_body, pre_enc, post_enc, "\\(oq", "\\(cq" }, /* So */
        { cond_body, pre_enc, post_enc, "\\(oq", "\\(cq" }, /* Sq */
        { NULL, pre_sm, NULL, NULL, NULL }, /* Sm */
        { NULL, pre_em, post_font, NULL, NULL }, /* Sx */
        { NULL, pre_sy, post_font, NULL, NULL }, /* Sy */
        { NULL, pre_li, post_font, NULL, NULL }, /* Tn */
        { NULL, NULL, NULL, NULL, NULL }, /* Ux */
        { NULL, NULL, NULL, NULL, NULL }, /* Xc */
        { NULL, NULL, NULL, NULL, NULL }, /* Xo */
        { NULL, pre_fo, post_fo, NULL, NULL }, /* Fo */
        { NULL, NULL, NULL, NULL, NULL }, /* Fc */
        { cond_body, pre_enc, post_enc, "[", "]" }, /* Oo */
        { NULL, NULL, NULL, NULL, NULL }, /* Oc */
        { NULL, pre_bk, post_bk, NULL, NULL }, /* Bk */
        { NULL, NULL, NULL, NULL, NULL }, /* Ek */
        { NULL, NULL, NULL, NULL, NULL }, /* Bt */
        { NULL, NULL, NULL, NULL, NULL }, /* Hf */
        { NULL, pre_em, post_font, NULL, NULL }, /* Fr */
        { NULL, NULL, NULL, NULL, NULL }, /* Ud */
        { NULL, NULL, post_lb, NULL, NULL }, /* Lb */
        { NULL, pre_abort, NULL, NULL, NULL }, /* Lp */
        { NULL, pre_lk, NULL, NULL, NULL }, /* Lk */
        { NULL, pre_em, post_font, NULL, NULL }, /* Mt */
        { cond_body, pre_enc, post_enc, "{", "}" }, /* Brq */
        { cond_body, pre_enc, post_enc, "{", "}" }, /* Bro */
        { NULL, NULL, NULL, NULL, NULL }, /* Brc */
        { NULL, NULL, post_percent, NULL, NULL }, /* %C */
        { NULL, pre_skip, NULL, NULL, NULL }, /* Es */
        { cond_body, pre_en, post_en, NULL, NULL }, /* En */
        { NULL, pre_bk, post_bk, NULL, NULL }, /* Dx */
        { NULL, NULL, post_percent, NULL, NULL }, /* %Q */
        { NULL, NULL, post_percent, NULL, NULL }, /* %U */
        { NULL, NULL, NULL, NULL, NULL }, /* Ta */
        { NULL, pre_skip, NULL, NULL, NULL }, /* Tg */
};
static const struct mdoc_man_act *mdoc_man_act(enum roff_tok);

static  int             outflags;
#define MMAN_spc        (1 << 0)  /* blank character before next word */
#define MMAN_spc_force  (1 << 1)  /* even before trailing punctuation */
#define MMAN_nl         (1 << 2)  /* break man(7) code line */
#define MMAN_br         (1 << 3)  /* break output line */
#define MMAN_sp         (1 << 4)  /* insert a blank output line */
#define MMAN_PP         (1 << 5)  /* reset indentation etc. */
#define MMAN_Sm         (1 << 6)  /* horizontal spacing mode */
#define MMAN_Bk         (1 << 7)  /* word keep mode */
#define MMAN_Bk_susp    (1 << 8)  /* suspend this (after a macro) */
#define MMAN_An_split   (1 << 9)  /* author mode is "split" */
#define MMAN_An_nosplit (1 << 10) /* author mode is "nosplit" */
#define MMAN_PD         (1 << 11) /* inter-paragraph spacing disabled */
#define MMAN_nbrword    (1 << 12) /* do not break the next word */

#define BL_STACK_MAX    32

static  int             Bl_stack[BL_STACK_MAX];  /* offsets [chars] */
static  int             Bl_stack_post[BL_STACK_MAX];  /* add final .RE */
static  int             Bl_stack_len;  /* number of nested Bl blocks */
static  int             TPremain;  /* characters before tag is full */

static  struct {
        char    *head;
        char    *tail;
        size_t   size;
}       fontqueue;


static const struct mdoc_man_act *
mdoc_man_act(enum roff_tok tok)
{
        assert(tok >= MDOC_Dd && tok <= MDOC_MAX);
        return mdoc_man_acts + (tok - MDOC_Dd);
}

static int
man_strlen(const char *cp)
{
        size_t   rsz;
        int      skip, sz;

        sz = 0;
        skip = 0;
        for (;;) {
                rsz = strcspn(cp, "\\");
                if (rsz) {
                        cp += rsz;
                        if (skip) {
                                skip = 0;
                                rsz--;
                        }
                        sz += rsz;
                }
                if ('\0' == *cp)
                        break;
                cp++;
                switch (mandoc_escape(&cp, NULL, NULL)) {
                case ESCAPE_ERROR:
                        return sz;
                case ESCAPE_UNICODE:
                case ESCAPE_NUMBERED:
                case ESCAPE_SPECIAL:
                case ESCAPE_UNDEF:
                case ESCAPE_OVERSTRIKE:
                        if (skip)
                                skip = 0;
                        else
                                sz++;
                        break;
                case ESCAPE_SKIPCHAR:
                        skip = 1;
                        break;
                default:
                        break;
                }
        }
        return sz;
}

static void
font_push(char newfont)
{

        if (fontqueue.head + fontqueue.size <= ++fontqueue.tail) {
                fontqueue.size += 8;
                fontqueue.head = mandoc_realloc(fontqueue.head,
                    fontqueue.size);
        }
        *fontqueue.tail = newfont;
        print_word("");
        printf("\\f");
        putchar(newfont);
        outflags &= ~MMAN_spc;
}

static void
font_pop(void)
{

        if (fontqueue.tail > fontqueue.head)
                fontqueue.tail--;
        outflags &= ~MMAN_spc;
        print_word("");
        printf("\\f");
        putchar(*fontqueue.tail);
}

static void
print_word(const char *s)
{

        if ((MMAN_PP | MMAN_sp | MMAN_br | MMAN_nl) & outflags) {
                /*
                 * If we need a newline, print it now and start afresh.
                 */
                if (MMAN_PP & outflags) {
                        if (MMAN_sp & outflags) {
                                if (MMAN_PD & outflags) {
                                        printf("\n.PD");
                                        outflags &= ~MMAN_PD;
                                }
                        } else if ( ! (MMAN_PD & outflags)) {
                                printf("\n.PD 0");
                                outflags |= MMAN_PD;
                        }
                        printf("\n.PP\n");
                } else if (MMAN_sp & outflags)
                        printf("\n.sp\n");
                else if (MMAN_br & outflags)
                        printf("\n.br\n");
                else if (MMAN_nl & outflags)
                        putchar('\n');
                outflags &= ~(MMAN_PP|MMAN_sp|MMAN_br|MMAN_nl|MMAN_spc);
                if (1 == TPremain)
                        printf(".br\n");
                TPremain = 0;
        } else if (MMAN_spc & outflags) {
                /*
                 * If we need a space, only print it if
                 * (1) it is forced by `No' or
                 * (2) what follows is not terminating punctuation or
                 * (3) what follows is longer than one character.
                 */
                if (MMAN_spc_force & outflags || '\0' == s[0] ||
                    NULL == strchr(".,:;)]?!", s[0]) || '\0' != s[1]) {
                        if (MMAN_Bk & outflags &&
                            ! (MMAN_Bk_susp & outflags))
                                putchar('\\');
                        putchar(' ');
                        if (TPremain)
                                TPremain--;
                }
        }

        /*
         * Reassign needing space if we're not following opening
         * punctuation.
         */
        if (MMAN_Sm & outflags && ('\0' == s[0] ||
            (('(' != s[0] && '[' != s[0]) || '\0' != s[1])))
                outflags |= MMAN_spc;
        else
                outflags &= ~MMAN_spc;
        outflags &= ~(MMAN_spc_force | MMAN_Bk_susp);

        for ( ; *s; s++) {
                switch (*s) {
                case ASCII_NBRSP:
                        printf("\\ ");
                        break;
                case ASCII_HYPH:
                        putchar('-');
                        break;
                case ASCII_BREAK:
                        printf("\\:");
                        break;
                case ' ':
                        if (MMAN_nbrword & outflags) {
                                printf("\\ ");
                                break;
                        }
                        /* FALLTHROUGH */
                default:
                        putchar((unsigned char)*s);
                        break;
                }
                if (TPremain)
                        TPremain--;
        }
        outflags &= ~MMAN_nbrword;
}

static void
print_line(const char *s, int newflags)
{

        outflags |= MMAN_nl;
        print_word(s);
        outflags |= newflags;
}

static void
print_block(const char *s, int newflags)
{

        outflags &= ~MMAN_PP;
        if (MMAN_sp & outflags) {
                outflags &= ~(MMAN_sp | MMAN_br);
                if (MMAN_PD & outflags) {
                        print_line(".PD", 0);
                        outflags &= ~MMAN_PD;
                }
        } else if (! (MMAN_PD & outflags))
                print_line(".PD 0", MMAN_PD);
        outflags |= MMAN_nl;
        print_word(s);
        outflags |= MMAN_Bk_susp | newflags;
}

static void
print_offs(const char *v, int keywords)
{
        char              buf[24];
        struct roffsu     su;
        const char       *end;
        int               sz;

        print_line(".RS", MMAN_Bk_susp);

        /* Convert v into a number (of characters). */
        if (NULL == v || '\0' == *v || (keywords && !strcmp(v, "left")))
                sz = 0;
        else if (keywords && !strcmp(v, "indent"))
                sz = 6;
        else if (keywords && !strcmp(v, "indent-two"))
                sz = 12;
        else {
                end = a2roffsu(v, &su, SCALE_EN);
                if (end == NULL || *end != '\0')
                        sz = man_strlen(v);
                else if (SCALE_EN == su.unit)
                        sz = su.scale;
                else {
                        /*
                         * XXX
                         * If we are inside an enclosing list,
                         * there is no easy way to add the two
                         * indentations because they are provided
                         * in terms of different units.
                         */
                        print_word(v);
                        outflags |= MMAN_nl;
                        return;
                }
        }

        /*
         * We are inside an enclosing list.
         * Add the two indentations.
         */
        if (Bl_stack_len)
                sz += Bl_stack[Bl_stack_len - 1];

        (void)snprintf(buf, sizeof(buf), "%dn", sz);
        print_word(buf);
        outflags |= MMAN_nl;
}

/*
 * Set up the indentation for a list item; used from pre_it().
 */
static void
print_width(const struct mdoc_bl *bl, const struct roff_node *child)
{
        char              buf[24];
        struct roffsu     su;
        const char       *end;
        int               numeric, remain, sz, chsz;

        numeric = 1;
        remain = 0;

        /* Convert the width into a number (of characters). */
        if (bl->width == NULL)
                sz = (bl->type == LIST_hang) ? 6 : 0;
        else {
                end = a2roffsu(bl->width, &su, SCALE_MAX);
                if (end == NULL || *end != '\0')
                        sz = man_strlen(bl->width);
                else if (SCALE_EN == su.unit)
                        sz = su.scale;
                else {
                        sz = 0;
                        numeric = 0;
                }
        }

        /* XXX Rough estimation, might have multiple parts. */
        if (bl->type == LIST_enum)
                chsz = (bl->count > 8) + 1;
        else if (child != NULL && child->type == ROFFT_TEXT)
                chsz = man_strlen(child->string);
        else
                chsz = 0;

        /* Maybe we are inside an enclosing list? */
        mid_it();

        /*
         * Save our own indentation,
         * such that child lists can use it.
         */
        Bl_stack[Bl_stack_len++] = sz + 2;

        /* Set up the current list. */
        if (chsz > sz && bl->type != LIST_tag)
                print_block(".HP", MMAN_spc);
        else {
                print_block(".TP", MMAN_spc);
                remain = sz + 2;
        }
        if (numeric) {
                (void)snprintf(buf, sizeof(buf), "%dn", sz + 2);
                print_word(buf);
        } else
                print_word(bl->width);
        TPremain = remain;
}

static void
print_count(int *count)
{
        char              buf[24];

        (void)snprintf(buf, sizeof(buf), "%d.\\&", ++*count);
        print_word(buf);
}

void
man_mdoc(void *arg, const struct roff_meta *mdoc)
{
        struct roff_node *n;

        printf(".\\\" Automatically generated from an mdoc input file."
            "  Do not edit.\n");
        for (n = mdoc->first->child; n != NULL; n = n->next) {
                if (n->type != ROFFT_COMMENT)
                        break;
                printf(".\\\"%s\n", n->string);
        }

        printf(".TH \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n",
            mdoc->title, (mdoc->msec == NULL ? "" : mdoc->msec),
            mdoc->date, mdoc->os, mdoc->vol);

        /* Disable hyphenation and if nroff, disable justification. */
        printf(".nh\n.if n .ad l");

        outflags = MMAN_nl | MMAN_Sm;
        if (0 == fontqueue.size) {
                fontqueue.size = 8;
                fontqueue.head = fontqueue.tail = mandoc_malloc(8);
                *fontqueue.tail = 'R';
        }
        for (; n != NULL; n = n->next)
                print_node(mdoc, n);
        putchar('\n');
}

static void
print_node(DECL_ARGS)
{
        const struct mdoc_man_act       *act;
        struct roff_node                *sub;
        int                              cond, do_sub;

        if (n->flags & NODE_NOPRT)
                return;

        /*
         * Break the line if we were parsed subsequent the current node.
         * This makes the page structure be more consistent.
         */
        if (outflags & MMAN_spc &&
            n->flags & NODE_LINE &&
            !roff_node_transparent(n))
                outflags |= MMAN_nl;

        act = NULL;
        cond = 0;
        do_sub = 1;
        n->flags &= ~NODE_ENDED;

        switch (n->type) {
        case ROFFT_EQN:
        case ROFFT_TBL:
                mandoc_msg(n->type == ROFFT_EQN ? MANDOCERR_EQN_TMAN :
                    MANDOCERR_TBL_TMAN, n->line, n->pos, NULL);
                outflags |= MMAN_PP | MMAN_sp | MMAN_nl;
                print_word("The");
                print_line(".B \\-T man", MMAN_nl);
                print_word("output mode does not support");
                print_word(n->type == ROFFT_EQN ? "eqn(7)" : "tbl(7)");
                print_word("input.");
                outflags |= MMAN_PP | MMAN_sp | MMAN_nl;
                return;
        case ROFFT_TEXT:
                /*
                 * Make sure that we don't happen to start with a
                 * control character at the start of a line.
                 */
                if (MMAN_nl & outflags &&
                    ('.' == *n->string || '\'' == *n->string)) {
                        print_word("");
                        printf("\\&");
                        outflags &= ~MMAN_spc;
                }
                if (n->flags & NODE_DELIMC)
                        outflags &= ~(MMAN_spc | MMAN_spc_force);
                else if (outflags & MMAN_Sm)
                        outflags |= MMAN_spc_force;
                print_word(n->string);
                if (n->flags & NODE_DELIMO)
                        outflags &= ~(MMAN_spc | MMAN_spc_force);
                else if (outflags & MMAN_Sm)
                        outflags |= MMAN_spc;
                break;
        default:
                if (n->tok < ROFF_MAX) {
                        (*roff_man_acts[n->tok])(meta, n);
                        return;
                }
                act = mdoc_man_act(n->tok);
                cond = act->cond == NULL || (*act->cond)(meta, n);
                if (cond && act->pre != NULL &&
                    (n->end == ENDBODY_NOT || n->child != NULL))
                        do_sub = (*act->pre)(meta, n);
                break;
        }

        /*
         * Conditionally run all child nodes.
         * Note that this iterates over children instead of using
         * recursion.  This prevents unnecessary depth in the stack.
         */
        if (do_sub)
                for (sub = n->child; sub; sub = sub->next)
                        print_node(meta, sub);

        /*
         * Lastly, conditionally run the post-node handler.
         */
        if (NODE_ENDED & n->flags)
                return;

        if (cond && act->post)
                (*act->post)(meta, n);

        if (ENDBODY_NOT != n->end)
                n->body->flags |= NODE_ENDED;
}

static int
cond_head(DECL_ARGS)
{

        return n->type == ROFFT_HEAD;
}

static int
cond_body(DECL_ARGS)
{

        return n->type == ROFFT_BODY;
}

static int
pre_abort(DECL_ARGS)
{
        abort();
}

static int
pre_enc(DECL_ARGS)
{
        const char      *prefix;

        prefix = mdoc_man_act(n->tok)->prefix;
        if (NULL == prefix)
                return 1;
        print_word(prefix);
        outflags &= ~MMAN_spc;
        return 1;
}

static void
post_enc(DECL_ARGS)
{
        const char *suffix;

        suffix = mdoc_man_act(n->tok)->suffix;
        if (NULL == suffix)
                return;
        outflags &= ~(MMAN_spc | MMAN_nl);
        print_word(suffix);
}

static int
pre_ex(DECL_ARGS)
{
        outflags |= MMAN_br | MMAN_nl;
        return 1;
}

static void
post_font(DECL_ARGS)
{

        font_pop();
}

static void
post_percent(DECL_ARGS)
{
        struct roff_node *np, *nn, *nnn;

        if (mdoc_man_act(n->tok)->pre == pre_em)
                font_pop();

        if ((nn = roff_node_next(n)) != NULL) {
                np = roff_node_prev(n);
                nnn = nn == NULL ? NULL : roff_node_next(nn);
                if (nn->tok != n->tok ||
                    (np != NULL && np->tok == n->tok) ||
                    (nnn != NULL && nnn->tok == n->tok))
                        print_word(",");
                if (nn->tok == n->tok &&
                    (nnn == NULL || nnn->tok != n->tok))
                        print_word("and");
        } else {
                print_word(".");
                outflags |= MMAN_nl;
        }
}

static int
pre__t(DECL_ARGS)
{

        if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T) {
                print_word("\\(lq");
                outflags &= ~MMAN_spc;
        } else
                font_push('I');
        return 1;
}

static void
post__t(DECL_ARGS)
{

        if (n->parent->tok  == MDOC_Rs && n->parent->norm->Rs.quote_T) {
                outflags &= ~MMAN_spc;
                print_word("\\(rq");
        } else
                font_pop();
        post_percent(meta, n);
}

/*
 * Print before a section header.
 */
static int
pre_sect(DECL_ARGS)
{

        if (n->type == ROFFT_HEAD) {
                outflags |= MMAN_sp;
                print_block(mdoc_man_act(n->tok)->prefix, 0);
                print_word("");
                putchar('\"');
                outflags &= ~MMAN_spc;
        }
        return 1;
}

/*
 * Print subsequent a section header.
 */
static void
post_sect(DECL_ARGS)
{

        if (n->type != ROFFT_HEAD)
                return;
        outflags &= ~MMAN_spc;
        print_word("");
        putchar('\"');
        outflags |= MMAN_nl;
        if (MDOC_Sh == n->tok && SEC_AUTHORS == n->sec)
                outflags &= ~(MMAN_An_split | MMAN_An_nosplit);
}

/* See mdoc_term.c, synopsis_pre() for comments. */
static void
pre_syn(struct roff_node *n)
{
        struct roff_node *np;

        if ((n->flags & NODE_SYNPRETTY) == 0 ||
            (np = roff_node_prev(n)) == NULL)
                return;

        if (np->tok == n->tok &&
            MDOC_Ft != n->tok &&
            MDOC_Fo != n->tok &&
            MDOC_Fn != n->tok) {
                outflags |= MMAN_br;
                return;
        }

        switch (np->tok) {
        case MDOC_Fd:
        case MDOC_Fn:
        case MDOC_Fo:
        case MDOC_In:
        case MDOC_Vt:
                outflags |= MMAN_sp;
                break;
        case MDOC_Ft:
                if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
                        outflags |= MMAN_sp;
                        break;
                }
                /* FALLTHROUGH */
        default:
                outflags |= MMAN_br;
                break;
        }
}

static int
pre_an(DECL_ARGS)
{

        switch (n->norm->An.auth) {
        case AUTH_split:
                outflags &= ~MMAN_An_nosplit;
                outflags |= MMAN_An_split;
                return 0;
        case AUTH_nosplit:
                outflags &= ~MMAN_An_split;
                outflags |= MMAN_An_nosplit;
                return 0;
        default:
                if (MMAN_An_split & outflags)
                        outflags |= MMAN_br;
                else if (SEC_AUTHORS == n->sec &&
                    ! (MMAN_An_nosplit & outflags))
                        outflags |= MMAN_An_split;
                return 1;
        }
}

static int
pre_ap(DECL_ARGS)
{

        outflags &= ~MMAN_spc;
        print_word("'");
        outflags &= ~MMAN_spc;
        return 0;
}

static int
pre_aq(DECL_ARGS)
{

        print_word(n->child != NULL && n->child->next == NULL &&
            n->child->tok == MDOC_Mt ?  "<" : "\\(la");
        outflags &= ~MMAN_spc;
        return 1;
}

static void
post_aq(DECL_ARGS)
{

        outflags &= ~(MMAN_spc | MMAN_nl);
        print_word(n->child != NULL && n->child->next == NULL &&
            n->child->tok == MDOC_Mt ?  ">" : "\\(ra");
}

static int
pre_bd(DECL_ARGS)
{
        outflags &= ~(MMAN_PP | MMAN_sp | MMAN_br);
        if (n->norm->Bd.type == DISP_unfilled ||
            n->norm->Bd.type == DISP_literal)
                print_line(".nf", 0);
        if (n->norm->Bd.comp == 0 && roff_node_prev(n->parent) != NULL)
                outflags |= MMAN_sp;
        print_offs(n->norm->Bd.offs, 1);
        return 1;
}

static void
post_bd(DECL_ARGS)
{
        enum roff_tok    bef, now;

        /* Close out this display. */
        print_line(".RE", MMAN_nl);
        bef = n->flags & NODE_NOFILL ? ROFF_nf : ROFF_fi;
        if (n->last == NULL)
                now = n->norm->Bd.type == DISP_unfilled ||
                    n->norm->Bd.type == DISP_literal ? ROFF_nf : ROFF_fi;
        else if (n->last->tok == ROFF_nf)
                now = ROFF_nf;
        else if (n->last->tok == ROFF_fi)
                now = ROFF_fi;
        else
                now = n->last->flags & NODE_NOFILL ? ROFF_nf : ROFF_fi;
        if (bef != now) {
                outflags |= MMAN_nl;
                print_word(".");
                outflags &= ~MMAN_spc;
                print_word(roff_name[bef]);
                outflags |= MMAN_nl;
        }

        /* Maybe we are inside an enclosing list? */
        if (roff_node_next(n->parent) != NULL)
                mid_it();
}

static int
pre_bf(DECL_ARGS)
{

        switch (n->type) {
        case ROFFT_BLOCK:
                return 1;
        case ROFFT_BODY:
                break;
        default:
                return 0;
        }
        switch (n->norm->Bf.font) {
        case FONT_Em:
                font_push('I');
                break;
        case FONT_Sy:
                font_push('B');
                break;
        default:
                font_push('R');
                break;
        }
        return 1;
}

static void
post_bf(DECL_ARGS)
{

        if (n->type == ROFFT_BODY)
                font_pop();
}

static int
pre_bk(DECL_ARGS)
{
        switch (n->type) {
        case ROFFT_BLOCK:
                return 1;
        case ROFFT_BODY:
        case ROFFT_ELEM:
                outflags |= MMAN_Bk;
                return 1;
        default:
                return 0;
        }
}

static void
post_bk(DECL_ARGS)
{
        switch (n->type) {
        case ROFFT_ELEM:
                while ((n = n->parent) != NULL)
                         if (n->tok == MDOC_Bk)
                                return;
                /* FALLTHROUGH */
        case ROFFT_BODY:
                outflags &= ~MMAN_Bk;
                break;
        default:
                break;
        }
}

static int
pre_bl(DECL_ARGS)
{
        size_t           icol;

        /*
         * print_offs() will increase the -offset to account for
         * a possible enclosing .It, but any enclosed .It blocks
         * just nest and do not add up their indentation.
         */
        if (n->norm->Bl.offs) {
                print_offs(n->norm->Bl.offs, 0);
                Bl_stack[Bl_stack_len++] = 0;
        }

        switch (n->norm->Bl.type) {
        case LIST_enum:
                n->norm->Bl.count = 0;
                return 1;
        case LIST_column:
                break;
        default:
                return 1;
        }

        if (n->child != NULL) {
                print_line(".TS", MMAN_nl);
                for (icol = 0; icol < n->norm->Bl.ncols; icol++)
                        print_word("l");
                print_word(".");
        }
        outflags |= MMAN_nl;
        return 1;
}

static void
post_bl(DECL_ARGS)
{

        switch (n->norm->Bl.type) {
        case LIST_column:
                if (n->child != NULL)
                        print_line(".TE", 0);
                break;
        case LIST_enum:
                n->norm->Bl.count = 0;
                break;
        default:
                break;
        }

        if (n->norm->Bl.offs) {
                print_line(".RE", MMAN_nl);
                assert(Bl_stack_len);
                Bl_stack_len--;
                assert(Bl_stack[Bl_stack_len] == 0);
        } else {
                outflags |= MMAN_PP | MMAN_nl;
                outflags &= ~(MMAN_sp | MMAN_br);
        }

        /* Maybe we are inside an enclosing list? */
        if (roff_node_next(n->parent) != NULL)
                mid_it();
}

static void
pre_br(DECL_ARGS)
{
        outflags |= MMAN_br;
}

static int
pre_dl(DECL_ARGS)
{
        print_offs("6n", 0);
        return 1;
}

static void
post_dl(DECL_ARGS)
{
        print_line(".RE", MMAN_nl);

        /* Maybe we are inside an enclosing list? */
        if (roff_node_next(n->parent) != NULL)
                mid_it();
}

static int
pre_em(DECL_ARGS)
{

        font_push('I');
        return 1;
}

static int
pre_en(DECL_ARGS)
{

        if (NULL == n->norm->Es ||
            NULL == n->norm->Es->child)
                return 1;

        print_word(n->norm->Es->child->string);
        outflags &= ~MMAN_spc;
        return 1;
}

static void
post_en(DECL_ARGS)
{

        if (NULL == n->norm->Es ||
            NULL == n->norm->Es->child ||
            NULL == n->norm->Es->child->next)
                return;

        outflags &= ~MMAN_spc;
        print_word(n->norm->Es->child->next->string);
        return;
}

static int
pre_eo(DECL_ARGS)
{

        if (n->end == ENDBODY_NOT &&
            n->parent->head->child == NULL &&
            n->child != NULL &&
            n->child->end != ENDBODY_NOT)
                print_word("\\&");
        else if (n->end != ENDBODY_NOT ? n->child != NULL :
            n->parent->head->child != NULL && (n->child != NULL ||
            (n->parent->tail != NULL && n->parent->tail->child != NULL)))
                outflags &= ~(MMAN_spc | MMAN_nl);
        return 1;
}

static void
post_eo(DECL_ARGS)
{
        int      body, tail;

        if (n->end != ENDBODY_NOT) {
                outflags |= MMAN_spc;
                return;
        }

        body = n->child != NULL || n->parent->head->child != NULL;
        tail = n->parent->tail != NULL && n->parent->tail->child != NULL;

        if (body && tail)
                outflags &= ~MMAN_spc;
        else if ( ! (body || tail))
                print_word("\\&");
        else if ( ! tail)
                outflags |= MMAN_spc;
}

static int
pre_fa(DECL_ARGS)
{
        int      am_Fa;

        am_Fa = MDOC_Fa == n->tok;

        if (am_Fa)
                n = n->child;

        while (NULL != n) {
                font_push('I');
                if (am_Fa || NODE_SYNPRETTY & n->flags)
                        outflags |= MMAN_nbrword;
                print_node(meta, n);
                font_pop();
                if (NULL != (n = n->next))
                        print_word(",");
        }
        return 0;
}

static void
post_fa(DECL_ARGS)
{
        struct roff_node *nn;

        if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa)
                print_word(",");
}

static int
pre_fd(DECL_ARGS)
{
        pre_syn(n);
        font_push('B');
        return 1;
}

static void
post_fd(DECL_ARGS)
{
        font_pop();
        outflags |= MMAN_br;
}

static int
pre_fl(DECL_ARGS)
{
        font_push('B');
        print_word("\\-");
        if (n->child != NULL)
                outflags &= ~MMAN_spc;
        return 1;
}

static void
post_fl(DECL_ARGS)
{
        struct roff_node *nn;

        font_pop();
        if (n->child == NULL &&
            ((nn = roff_node_next(n)) != NULL &&
            nn->type != ROFFT_TEXT &&
            (nn->flags & NODE_LINE) == 0))
                outflags &= ~MMAN_spc;
}

static int
pre_fn(DECL_ARGS)
{

        pre_syn(n);

        n = n->child;
        if (NULL == n)
                return 0;

        if (NODE_SYNPRETTY & n->flags)
                print_block(".HP 4n", MMAN_nl);

        font_push('B');
        print_node(meta, n);
        font_pop();
        outflags &= ~MMAN_spc;
        print_word("(");
        outflags &= ~MMAN_spc;

        n = n->next;
        if (NULL != n)
                pre_fa(meta, n);
        return 0;
}

static void
post_fn(DECL_ARGS)
{

        print_word(")");
        if (NODE_SYNPRETTY & n->flags) {
                print_word(";");
                outflags |= MMAN_PP;
        }
}

static int
pre_fo(DECL_ARGS)
{

        switch (n->type) {
        case ROFFT_BLOCK:
                pre_syn(n);
                break;
        case ROFFT_HEAD:
                if (n->child == NULL)
                        return 0;
                if (NODE_SYNPRETTY & n->flags)
                        print_block(".HP 4n", MMAN_nl);
                font_push('B');
                break;
        case ROFFT_BODY:
                outflags &= ~(MMAN_spc | MMAN_nl);
                print_word("(");
                outflags &= ~MMAN_spc;
                break;
        default:
                break;
        }
        return 1;
}

static void
post_fo(DECL_ARGS)
{

        switch (n->type) {
        case ROFFT_HEAD:
                if (n->child != NULL)
                        font_pop();
                break;
        case ROFFT_BODY:
                post_fn(meta, n);
                break;
        default:
                break;
        }
}

static int
pre_Ft(DECL_ARGS)
{

        pre_syn(n);
        font_push('I');
        return 1;
}

static void
pre_ft(DECL_ARGS)
{
        print_line(".ft", 0);
        print_word(n->child->string);
        outflags |= MMAN_nl;
}

static int
pre_in(DECL_ARGS)
{

        if (NODE_SYNPRETTY & n->flags) {
                pre_syn(n);
                font_push('B');
                print_word("#include <");
                outflags &= ~MMAN_spc;
        } else {
                print_word("<");
                outflags &= ~MMAN_spc;
                font_push('I');
        }
        return 1;
}

static void
post_in(DECL_ARGS)
{

        if (NODE_SYNPRETTY & n->flags) {
                outflags &= ~MMAN_spc;
                print_word(">");
                font_pop();
                outflags |= MMAN_br;
        } else {
                font_pop();
                outflags &= ~MMAN_spc;
                print_word(">");
        }
}

static int
pre_it(DECL_ARGS)
{
        const struct roff_node *bln;

        switch (n->type) {
        case ROFFT_HEAD:
                outflags |= MMAN_PP | MMAN_nl;
                bln = n->parent->parent;
                if (bln->norm->Bl.comp == 0 ||
                    (n->parent->prev == NULL &&
                     roff_node_prev(bln->parent) == NULL))
                        outflags |= MMAN_sp;
                outflags &= ~MMAN_br;
                switch (bln->norm->Bl.type) {
                case LIST_item:
                        return 0;
                case LIST_inset:
                case LIST_diag:
                case LIST_ohang:
                        if (bln->norm->Bl.type == LIST_diag)
                                print_line(".B \"", 0);
                        else
                                print_line(".BR \\& \"", 0);
                        outflags &= ~MMAN_spc;
                        return 1;
                case LIST_bullet:
                case LIST_dash:
                case LIST_hyphen:
                        print_width(&bln->norm->Bl, NULL);
                        TPremain = 0;
                        outflags |= MMAN_nl;
                        font_push('B');
                        if (LIST_bullet == bln->norm->Bl.type)
                                print_word("\\(bu");
                        else
                                print_word("-");
                        font_pop();
                        outflags |= MMAN_nl;
                        return 0;
                case LIST_enum:
                        print_width(&bln->norm->Bl, NULL);
                        TPremain = 0;
                        outflags |= MMAN_nl;
                        print_count(&bln->norm->Bl.count);
                        outflags |= MMAN_nl;
                        return 0;
                case LIST_hang:
                        print_width(&bln->norm->Bl, n->child);
                        TPremain = 0;
                        outflags |= MMAN_nl;
                        return 1;
                case LIST_tag:
                        print_width(&bln->norm->Bl, n->child);
                        putchar('\n');
                        outflags &= ~MMAN_spc;
                        return 1;
                default:
                        return 1;
                }
        default:
                break;
        }
        return 1;
}

/*
 * This function is called after closing out an indented block.
 * If we are inside an enclosing list, restore its indentation.
 */
static void
mid_it(void)
{
        char             buf[24];

        /* Nothing to do outside a list. */
        if (0 == Bl_stack_len || 0 == Bl_stack[Bl_stack_len - 1])
                return;

        /* The indentation has already been set up. */
        if (Bl_stack_post[Bl_stack_len - 1])
                return;

        /* Restore the indentation of the enclosing list. */
        print_line(".RS", MMAN_Bk_susp);
        (void)snprintf(buf, sizeof(buf), "%dn",
            Bl_stack[Bl_stack_len - 1]);
        print_word(buf);

        /* Remeber to close out this .RS block later. */
        Bl_stack_post[Bl_stack_len - 1] = 1;
}

static void
post_it(DECL_ARGS)
{
        const struct roff_node *bln;

        bln = n->parent->parent;

        switch (n->type) {
        case ROFFT_HEAD:
                switch (bln->norm->Bl.type) {
                case LIST_diag:
                        outflags &= ~MMAN_spc;
                        print_word("\\ ");
                        break;
                case LIST_ohang:
                        outflags |= MMAN_br;
                        break;
                default:
                        break;
                }
                break;
        case ROFFT_BODY:
                switch (bln->norm->Bl.type) {
                case LIST_bullet:
                case LIST_dash:
                case LIST_hyphen:
                case LIST_enum:
                case LIST_hang:
                case LIST_tag:
                        assert(Bl_stack_len);
                        Bl_stack[--Bl_stack_len] = 0;

                        /*
                         * Our indentation had to be restored
                         * after a child display or child list.
                         * Close out that indentation block now.
                         */
                        if (Bl_stack_post[Bl_stack_len]) {
                                print_line(".RE", MMAN_nl);
                                Bl_stack_post[Bl_stack_len] = 0;
                        }
                        break;
                case LIST_column:
                        if (NULL != n->next) {
                                putchar('\t');
                                outflags &= ~MMAN_spc;
                        }
                        break;
                default:
                        break;
                }
                break;
        default:
                break;
        }
}

static void
post_lb(DECL_ARGS)
{

        if (SEC_LIBRARY == n->sec)
                outflags |= MMAN_br;
}

static int
pre_lk(DECL_ARGS)
{
        const struct roff_node *link, *descr, *punct;

        if ((link = n->child) == NULL)
                return 0;

        /* Find beginning of trailing punctuation. */
        punct = n->last;
        while (punct != link && punct->flags & NODE_DELIMC)
                punct = punct->prev;
        punct = punct->next;

        /* Link text. */
        if ((descr = link->next) != NULL && descr != punct) {
                font_push('I');
                while (descr != punct) {
                        print_word(descr->string);
                        descr = descr->next;
                }
                font_pop();
                print_word(":");
        }

        /* Link target. */
        font_push('B');
        print_word(link->string);
        font_pop();

        /* Trailing punctuation. */
        while (punct != NULL) {
                print_word(punct->string);
                punct = punct->next;
        }
        return 0;
}

static void
pre_onearg(DECL_ARGS)
{
        outflags |= MMAN_nl;
        print_word(".");
        outflags &= ~MMAN_spc;
        print_word(roff_name[n->tok]);
        if (n->child != NULL)
                print_word(n->child->string);
        outflags |= MMAN_nl;
        if (n->tok == ROFF_ce)
                for (n = n->child->next; n != NULL; n = n->next)
                        print_node(meta, n);
}

static int
pre_li(DECL_ARGS)
{
        font_push('R');
        return 1;
}

static int
pre_nm(DECL_ARGS)
{
        char    *name;

        switch (n->type) {
        case ROFFT_BLOCK:
                outflags |= MMAN_Bk;
                pre_syn(n);
                return 1;
        case ROFFT_HEAD:
        case ROFFT_ELEM:
                break;
        default:
                return 1;
        }
        name = n->child == NULL ? NULL : n->child->string;
        if (name == NULL)
                return 0;
        if (n->type == ROFFT_HEAD) {
                if (roff_node_prev(n->parent) == NULL)
                        outflags |= MMAN_sp;
                print_block(".HP", 0);
                printf(" %dn", man_strlen(name) + 1);
                outflags |= MMAN_nl;
        }
        font_push('B');
        return 1;
}

static void
post_nm(DECL_ARGS)
{
        switch (n->type) {
        case ROFFT_BLOCK:
                outflags &= ~MMAN_Bk;
                break;
        case ROFFT_HEAD:
        case ROFFT_ELEM:
                if (n->child != NULL && n->child->string != NULL)
                        font_pop();
                break;
        default:
                break;
        }
}

static int
pre_no(DECL_ARGS)
{
        outflags |= MMAN_spc_force;
        return 1;
}

static void
pre_noarg(DECL_ARGS)
{
        outflags |= MMAN_nl;
        print_word(".");
        outflags &= ~MMAN_spc;
        print_word(roff_name[n->tok]);
        outflags |= MMAN_nl;
}

static int
pre_ns(DECL_ARGS)
{
        outflags &= ~MMAN_spc;
        return 0;
}

static void
post_pf(DECL_ARGS)
{

        if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
                outflags &= ~MMAN_spc;
}

static int
pre_pp(DECL_ARGS)
{

        if (MDOC_It != n->parent->tok)
                outflags |= MMAN_PP;
        outflags |= MMAN_sp | MMAN_nl;
        outflags &= ~MMAN_br;
        return 0;
}

static int
pre_rs(DECL_ARGS)
{

        if (SEC_SEE_ALSO == n->sec) {
                outflags |= MMAN_PP | MMAN_sp | MMAN_nl;
                outflags &= ~MMAN_br;
        }
        return 1;
}

static int
pre_skip(DECL_ARGS)
{

        return 0;
}

static int
pre_sm(DECL_ARGS)
{

        if (NULL == n->child)
                outflags ^= MMAN_Sm;
        else if (0 == strcmp("on", n->child->string))
                outflags |= MMAN_Sm;
        else
                outflags &= ~MMAN_Sm;

        if (MMAN_Sm & outflags)
                outflags |= MMAN_spc;

        return 0;
}

static void
pre_sp(DECL_ARGS)
{
        if (outflags & MMAN_PP) {
                outflags &= ~MMAN_PP;
                print_line(".PP", 0);
        } else {
                print_line(".sp", 0);
                if (n->child != NULL)
                        print_word(n->child->string);
        }
        outflags |= MMAN_nl;
}

static int
pre_sy(DECL_ARGS)
{

        font_push('B');
        return 1;
}

static void
pre_ta(DECL_ARGS)
{
        print_line(".ta", 0);
        for (n = n->child; n != NULL; n = n->next)
                print_word(n->string);
        outflags |= MMAN_nl;
}

static int
pre_vt(DECL_ARGS)
{

        if (NODE_SYNPRETTY & n->flags) {
                switch (n->type) {
                case ROFFT_BLOCK:
                        pre_syn(n);
                        return 1;
                case ROFFT_BODY:
                        break;
                default:
                        return 0;
                }
        }
        font_push('I');
        return 1;
}

static void
post_vt(DECL_ARGS)
{

        if (n->flags & NODE_SYNPRETTY && n->type != ROFFT_BODY)
                return;
        font_pop();
}

static int
pre_xr(DECL_ARGS)
{

        n = n->child;
        if (NULL == n)
                return 0;
        print_node(meta, n);
        n = n->next;
        if (NULL == n)
                return 0;
        outflags &= ~MMAN_spc;
        print_word("(");
        print_node(meta, n);
        print_word(")");
        return 0;
}