mdoc_term.c revision 698f87a48e2e945bfe5493ce168e0d0ae1cedd5c
2N/A/* $Id: mdoc_term.c,v 1.258 2013/12/25 21:24:12 schwarze Exp $ */
2N/A/*
2N/A * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
2N/A * Copyright (c) 2010, 2012, 2013 Ingo Schwarze <schwarze@openbsd.org>
2N/A * Copyright (c) 2013 Franco Fichtner <franco@lastsummer.de>
2N/A *
2N/A * Permission to use, copy, modify, and distribute this software for any
2N/A * purpose with or without fee is hereby granted, provided that the above
2N/A * copyright notice and this permission notice appear in all copies.
2N/A *
2N/A * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
2N/A * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
2N/A * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
2N/A * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
2N/A * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
2N/A * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
2N/A * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
2N/A */
2N/A#ifdef HAVE_CONFIG_H
2N/A#include "config.h"
2N/A#endif
2N/A
2N/A#include <sys/types.h>
2N/A
2N/A#include <assert.h>
2N/A#include <ctype.h>
2N/A#include <stdint.h>
2N/A#include <stdio.h>
2N/A#include <stdlib.h>
2N/A#include <string.h>
2N/A
2N/A#include "mandoc.h"
2N/A#include "out.h"
2N/A#include "term.h"
2N/A#include "mdoc.h"
2N/A#include "main.h"
2N/A
2N/Astruct termpair {
2N/A struct termpair *ppair;
2N/A int count;
2N/A};
2N/A
2N/A#define DECL_ARGS struct termp *p, \
2N/A struct termpair *pair, \
2N/A const struct mdoc_meta *meta, \
2N/A struct mdoc_node *n
2N/A
2N/Astruct termact {
2N/A int (*pre)(DECL_ARGS);
2N/A void (*post)(DECL_ARGS);
2N/A};
2N/A
2N/Astatic size_t a2width(const struct termp *, const char *);
2N/Astatic size_t a2height(const struct termp *, const char *);
2N/Astatic size_t a2offs(const struct termp *, const char *);
2N/A
2N/Astatic void print_bvspace(struct termp *,
2N/A const struct mdoc_node *,
2N/A const struct mdoc_node *);
2N/Astatic void print_mdoc_node(DECL_ARGS);
2N/Astatic void print_mdoc_nodelist(DECL_ARGS);
2N/Astatic void print_mdoc_head(struct termp *, const void *);
2N/Astatic void print_mdoc_foot(struct termp *, const void *);
2N/Astatic void synopsis_pre(struct termp *,
2N/A const struct mdoc_node *);
2N/A
2N/Astatic void termp____post(DECL_ARGS);
2N/Astatic void termp__t_post(DECL_ARGS);
2N/Astatic void termp_an_post(DECL_ARGS);
2N/Astatic void termp_bd_post(DECL_ARGS);
2N/Astatic void termp_bk_post(DECL_ARGS);
2N/Astatic void termp_bl_post(DECL_ARGS);
2N/Astatic void termp_fd_post(DECL_ARGS);
2N/Astatic void termp_fo_post(DECL_ARGS);
2N/Astatic void termp_in_post(DECL_ARGS);
2N/Astatic void termp_it_post(DECL_ARGS);
2N/Astatic void termp_lb_post(DECL_ARGS);
2N/Astatic void termp_nm_post(DECL_ARGS);
2N/Astatic void termp_pf_post(DECL_ARGS);
2N/Astatic void termp_quote_post(DECL_ARGS);
2N/Astatic void termp_sh_post(DECL_ARGS);
2N/Astatic void termp_ss_post(DECL_ARGS);
2N/A
2N/Astatic int termp__a_pre(DECL_ARGS);
2N/Astatic int termp__t_pre(DECL_ARGS);
2N/Astatic int termp_an_pre(DECL_ARGS);
2N/Astatic int termp_ap_pre(DECL_ARGS);
2N/Astatic int termp_bd_pre(DECL_ARGS);
2N/Astatic int termp_bf_pre(DECL_ARGS);
2N/Astatic int termp_bk_pre(DECL_ARGS);
2N/Astatic int termp_bl_pre(DECL_ARGS);
2N/Astatic int termp_bold_pre(DECL_ARGS);
2N/Astatic int termp_bt_pre(DECL_ARGS);
2N/Astatic int termp_bx_pre(DECL_ARGS);
2N/Astatic int termp_cd_pre(DECL_ARGS);
2N/Astatic int termp_d1_pre(DECL_ARGS);
2N/Astatic int termp_ex_pre(DECL_ARGS);
2N/Astatic int termp_fa_pre(DECL_ARGS);
2N/Astatic int termp_fd_pre(DECL_ARGS);
2N/Astatic int termp_fl_pre(DECL_ARGS);
2N/Astatic int termp_fn_pre(DECL_ARGS);
2N/Astatic int termp_fo_pre(DECL_ARGS);
2N/Astatic int termp_ft_pre(DECL_ARGS);
2N/Astatic int termp_in_pre(DECL_ARGS);
2N/Astatic int termp_it_pre(DECL_ARGS);
2N/Astatic int termp_li_pre(DECL_ARGS);
2N/Astatic int termp_lk_pre(DECL_ARGS);
2N/Astatic int termp_nd_pre(DECL_ARGS);
2N/Astatic int termp_nm_pre(DECL_ARGS);
2N/Astatic int termp_ns_pre(DECL_ARGS);
2N/Astatic int termp_quote_pre(DECL_ARGS);
2N/Astatic int termp_rs_pre(DECL_ARGS);
2N/Astatic int termp_rv_pre(DECL_ARGS);
2N/Astatic int termp_sh_pre(DECL_ARGS);
2N/Astatic int termp_sm_pre(DECL_ARGS);
2N/Astatic int termp_sp_pre(DECL_ARGS);
2N/Astatic int termp_ss_pre(DECL_ARGS);
2N/Astatic int termp_under_pre(DECL_ARGS);
2N/Astatic int termp_ud_pre(DECL_ARGS);
2N/Astatic int termp_vt_pre(DECL_ARGS);
2N/Astatic int termp_xr_pre(DECL_ARGS);
2N/Astatic int termp_xx_pre(DECL_ARGS);
2N/A
2N/Astatic const struct termact termacts[MDOC_MAX] = {
2N/A { termp_ap_pre, NULL }, /* Ap */
2N/A { NULL, NULL }, /* Dd */
2N/A { NULL, NULL }, /* Dt */
2N/A { NULL, NULL }, /* Os */
2N/A { termp_sh_pre, termp_sh_post }, /* Sh */
2N/A { termp_ss_pre, termp_ss_post }, /* Ss */
2N/A { termp_sp_pre, NULL }, /* Pp */
2N/A { termp_d1_pre, termp_bl_post }, /* D1 */
2N/A { termp_d1_pre, termp_bl_post }, /* Dl */
2N/A { termp_bd_pre, termp_bd_post }, /* Bd */
2N/A { NULL, NULL }, /* Ed */
2N/A { termp_bl_pre, termp_bl_post }, /* Bl */
2N/A { NULL, NULL }, /* El */
2N/A { termp_it_pre, termp_it_post }, /* It */
2N/A { termp_under_pre, NULL }, /* Ad */
2N/A { termp_an_pre, termp_an_post }, /* An */
2N/A { termp_under_pre, NULL }, /* Ar */
2N/A { termp_cd_pre, NULL }, /* Cd */
2N/A { termp_bold_pre, NULL }, /* Cm */
2N/A { NULL, NULL }, /* Dv */
2N/A { NULL, NULL }, /* Er */
2N/A { NULL, NULL }, /* Ev */
2N/A { termp_ex_pre, NULL }, /* Ex */
2N/A { termp_fa_pre, NULL }, /* Fa */
2N/A { termp_fd_pre, termp_fd_post }, /* Fd */
2N/A { termp_fl_pre, NULL }, /* Fl */
2N/A { termp_fn_pre, NULL }, /* Fn */
2N/A { termp_ft_pre, NULL }, /* Ft */
2N/A { termp_bold_pre, NULL }, /* Ic */
2N/A { termp_in_pre, termp_in_post }, /* In */
2N/A { termp_li_pre, NULL }, /* Li */
2N/A { termp_nd_pre, NULL }, /* Nd */
2N/A { termp_nm_pre, termp_nm_post }, /* Nm */
2N/A { termp_quote_pre, termp_quote_post }, /* Op */
2N/A { NULL, NULL }, /* Ot */
2N/A { termp_under_pre, NULL }, /* Pa */
2N/A { termp_rv_pre, NULL }, /* Rv */
2N/A { NULL, NULL }, /* St */
2N/A { termp_under_pre, NULL }, /* Va */
2N/A { termp_vt_pre, NULL }, /* Vt */
2N/A { termp_xr_pre, NULL }, /* Xr */
2N/A { termp__a_pre, termp____post }, /* %A */
2N/A { termp_under_pre, termp____post }, /* %B */
2N/A { NULL, termp____post }, /* %D */
2N/A { termp_under_pre, termp____post }, /* %I */
2N/A { termp_under_pre, termp____post }, /* %J */
2N/A { NULL, termp____post }, /* %N */
2N/A { NULL, termp____post }, /* %O */
2N/A { NULL, termp____post }, /* %P */
2N/A { NULL, termp____post }, /* %R */
2N/A { termp__t_pre, termp__t_post }, /* %T */
2N/A { NULL, termp____post }, /* %V */
2N/A { NULL, NULL }, /* Ac */
2N/A { termp_quote_pre, termp_quote_post }, /* Ao */
2N/A { termp_quote_pre, termp_quote_post }, /* Aq */
2N/A { NULL, NULL }, /* At */
2N/A { NULL, NULL }, /* Bc */
2N/A { termp_bf_pre, NULL }, /* Bf */
2N/A { termp_quote_pre, termp_quote_post }, /* Bo */
2N/A { termp_quote_pre, termp_quote_post }, /* Bq */
2N/A { termp_xx_pre, NULL }, /* Bsx */
2N/A { termp_bx_pre, NULL }, /* Bx */
2N/A { NULL, NULL }, /* Db */
2N/A { NULL, NULL }, /* Dc */
2N/A { termp_quote_pre, termp_quote_post }, /* Do */
2N/A { termp_quote_pre, termp_quote_post }, /* Dq */
2N/A { NULL, NULL }, /* Ec */ /* FIXME: no space */
2N/A { NULL, NULL }, /* Ef */
2N/A { termp_under_pre, NULL }, /* Em */
2N/A { termp_quote_pre, termp_quote_post }, /* Eo */
2N/A { termp_xx_pre, NULL }, /* Fx */
2N/A { termp_bold_pre, NULL }, /* Ms */
2N/A { NULL, NULL }, /* No */
2N/A { termp_ns_pre, NULL }, /* Ns */
2N/A { termp_xx_pre, NULL }, /* Nx */
2N/A { termp_xx_pre, NULL }, /* Ox */
2N/A { NULL, NULL }, /* Pc */
2N/A { NULL, termp_pf_post }, /* Pf */
2N/A { termp_quote_pre, termp_quote_post }, /* Po */
2N/A { termp_quote_pre, termp_quote_post }, /* Pq */
2N/A { NULL, NULL }, /* Qc */
2N/A { termp_quote_pre, termp_quote_post }, /* Ql */
2N/A { termp_quote_pre, termp_quote_post }, /* Qo */
2N/A { termp_quote_pre, termp_quote_post }, /* Qq */
2N/A { NULL, NULL }, /* Re */
2N/A { termp_rs_pre, NULL }, /* Rs */
2N/A { NULL, NULL }, /* Sc */
2N/A { termp_quote_pre, termp_quote_post }, /* So */
2N/A { termp_quote_pre, termp_quote_post }, /* Sq */
2N/A { termp_sm_pre, NULL }, /* Sm */
2N/A { termp_under_pre, NULL }, /* Sx */
2N/A { termp_bold_pre, NULL }, /* Sy */
2N/A { NULL, NULL }, /* Tn */
2N/A { termp_xx_pre, NULL }, /* Ux */
2N/A { NULL, NULL }, /* Xc */
2N/A { NULL, NULL }, /* Xo */
2N/A { termp_fo_pre, termp_fo_post }, /* Fo */
2N/A { NULL, NULL }, /* Fc */
2N/A { termp_quote_pre, termp_quote_post }, /* Oo */
2N/A { NULL, NULL }, /* Oc */
2N/A { termp_bk_pre, termp_bk_post }, /* Bk */
2N/A { NULL, NULL }, /* Ek */
2N/A { termp_bt_pre, NULL }, /* Bt */
2N/A { NULL, NULL }, /* Hf */
2N/A { NULL, NULL }, /* Fr */
2N/A { termp_ud_pre, NULL }, /* Ud */
2N/A { NULL, termp_lb_post }, /* Lb */
2N/A { termp_sp_pre, NULL }, /* Lp */
2N/A { termp_lk_pre, NULL }, /* Lk */
2N/A { termp_under_pre, NULL }, /* Mt */
2N/A { termp_quote_pre, termp_quote_post }, /* Brq */
2N/A { termp_quote_pre, termp_quote_post }, /* Bro */
2N/A { NULL, NULL }, /* Brc */
2N/A { NULL, termp____post }, /* %C */
2N/A { NULL, NULL }, /* Es */ /* TODO */
2N/A { NULL, NULL }, /* En */ /* TODO */
2N/A { termp_xx_pre, NULL }, /* Dx */
2N/A { NULL, termp____post }, /* %Q */
2N/A { termp_sp_pre, NULL }, /* br */
2N/A { termp_sp_pre, NULL }, /* sp */
2N/A { NULL, termp____post }, /* %U */
2N/A { NULL, NULL }, /* Ta */
2N/A};
2N/A
2N/A
2N/Avoid
2N/Aterminal_mdoc(void *arg, const struct mdoc *mdoc)
2N/A{
2N/A const struct mdoc_node *n;
2N/A const struct mdoc_meta *meta;
2N/A struct termp *p;
2N/A
2N/A p = (struct termp *)arg;
2N/A
2N/A if (0 == p->defindent)
2N/A p->defindent = 5;
2N/A
2N/A p->overstep = 0;
2N/A p->maxrmargin = p->defrmargin;
2N/A p->tabwidth = term_len(p, 5);
2N/A
2N/A if (NULL == p->symtab)
2N/A p->symtab = mchars_alloc();
2N/A
2N/A n = mdoc_node(mdoc);
2N/A meta = mdoc_meta(mdoc);
2N/A
2N/A term_begin(p, print_mdoc_head, print_mdoc_foot, meta);
2N/A
2N/A if (n->child)
2N/A print_mdoc_nodelist(p, NULL, meta, n->child);
2N/A
2N/A term_end(p);
2N/A}
2N/A
2N/A
2N/Astatic void
2N/Aprint_mdoc_nodelist(DECL_ARGS)
2N/A{
2N/A
2N/A print_mdoc_node(p, pair, meta, n);
2N/A if (n->next)
2N/A print_mdoc_nodelist(p, pair, meta, n->next);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic void
2N/Aprint_mdoc_node(DECL_ARGS)
2N/A{
2N/A int chld;
2N/A struct termpair npair;
2N/A size_t offset, rmargin;
2N/A
2N/A chld = 1;
2N/A offset = p->offset;
2N/A rmargin = p->rmargin;
2N/A n->prev_font = term_fontq(p);
2N/A
2N/A memset(&npair, 0, sizeof(struct termpair));
2N/A npair.ppair = pair;
2N/A
2N/A /*
2N/A * Keeps only work until the end of a line. If a keep was
2N/A * invoked in a prior line, revert it to PREKEEP.
2N/A */
2N/A
2N/A if (TERMP_KEEP & p->flags) {
2N/A if (n->prev ? (n->prev->lastline != n->line) :
2N/A (n->parent && n->parent->line != n->line)) {
2N/A p->flags &= ~TERMP_KEEP;
2N/A p->flags |= TERMP_PREKEEP;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * After the keep flags have been set up, we may now
2N/A * produce output. Note that some pre-handlers do so.
2N/A */
2N/A
2N/A switch (n->type) {
2N/A case (MDOC_TEXT):
2N/A if (' ' == *n->string && MDOC_LINE & n->flags)
2N/A term_newln(p);
2N/A if (MDOC_DELIMC & n->flags)
2N/A p->flags |= TERMP_NOSPACE;
2N/A term_word(p, n->string);
2N/A if (MDOC_DELIMO & n->flags)
2N/A p->flags |= TERMP_NOSPACE;
2N/A break;
2N/A case (MDOC_EQN):
2N/A term_eqn(p, n->eqn);
2N/A break;
2N/A case (MDOC_TBL):
2N/A term_tbl(p, n->span);
2N/A break;
2N/A default:
2N/A if (termacts[n->tok].pre && ENDBODY_NOT == n->end)
2N/A chld = (*termacts[n->tok].pre)
2N/A (p, &npair, meta, n);
2N/A break;
2N/A }
2N/A
2N/A if (chld && n->child)
2N/A print_mdoc_nodelist(p, &npair, meta, n->child);
2N/A
2N/A term_fontpopq(p,
2N/A (ENDBODY_NOT == n->end ? n : n->pending)->prev_font);
2N/A
2N/A switch (n->type) {
2N/A case (MDOC_TEXT):
2N/A break;
2N/A case (MDOC_TBL):
2N/A break;
2N/A case (MDOC_EQN):
2N/A break;
2N/A default:
2N/A if ( ! termacts[n->tok].post || MDOC_ENDED & n->flags)
2N/A break;
2N/A (void)(*termacts[n->tok].post)(p, &npair, meta, n);
2N/A
2N/A /*
2N/A * Explicit end tokens not only call the post
2N/A * handler, but also tell the respective block
2N/A * that it must not call the post handler again.
2N/A */
2N/A if (ENDBODY_NOT != n->end)
2N/A n->pending->flags |= MDOC_ENDED;
2N/A
2N/A /*
2N/A * End of line terminating an implicit block
2N/A * while an explicit block is still open.
2N/A * Continue the explicit block without spacing.
2N/A */
2N/A if (ENDBODY_NOSPACE == n->end)
2N/A p->flags |= TERMP_NOSPACE;
2N/A break;
2N/A }
2N/A
2N/A if (MDOC_EOS & n->flags)
2N/A p->flags |= TERMP_SENTENCE;
2N/A
2N/A p->offset = offset;
2N/A p->rmargin = rmargin;
2N/A}
2N/A
2N/A
2N/Astatic void
2N/Aprint_mdoc_foot(struct termp *p, const void *arg)
2N/A{
2N/A const struct mdoc_meta *meta;
2N/A
2N/A meta = (const struct mdoc_meta *)arg;
2N/A
2N/A term_fontrepl(p, TERMFONT_NONE);
2N/A
2N/A /*
2N/A * Output the footer in new-groff style, that is, three columns
2N/A * with the middle being the manual date and flanking columns
2N/A * being the operating system:
2N/A *
2N/A * SYSTEM DATE SYSTEM
2N/A */
2N/A
2N/A term_vspace(p);
2N/A
2N/A p->offset = 0;
2N/A p->rmargin = (p->maxrmargin -
2N/A term_strlen(p, meta->date) + term_len(p, 1)) / 2;
2N/A p->trailspace = 1;
2N/A p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
2N/A
2N/A term_word(p, meta->os);
2N/A term_flushln(p);
2N/A
2N/A p->offset = p->rmargin;
2N/A p->rmargin = p->maxrmargin - term_strlen(p, meta->os);
2N/A p->flags |= TERMP_NOSPACE;
2N/A
2N/A term_word(p, meta->date);
2N/A term_flushln(p);
2N/A
2N/A p->offset = p->rmargin;
2N/A p->rmargin = p->maxrmargin;
2N/A p->trailspace = 0;
2N/A p->flags &= ~TERMP_NOBREAK;
2N/A p->flags |= TERMP_NOSPACE;
2N/A
2N/A term_word(p, meta->os);
2N/A term_flushln(p);
2N/A
2N/A p->offset = 0;
2N/A p->rmargin = p->maxrmargin;
2N/A p->flags = 0;
2N/A}
2N/A
2N/A
2N/Astatic void
2N/Aprint_mdoc_head(struct termp *p, const void *arg)
2N/A{
2N/A char buf[BUFSIZ], title[BUFSIZ];
2N/A size_t buflen, titlen;
2N/A const struct mdoc_meta *meta;
2N/A
2N/A meta = (const struct mdoc_meta *)arg;
2N/A
2N/A /*
2N/A * The header is strange. It has three components, which are
2N/A * really two with the first duplicated. It goes like this:
2N/A *
2N/A * IDENTIFIER TITLE IDENTIFIER
2N/A *
2N/A * The IDENTIFIER is NAME(SECTION), which is the command-name
2N/A * (if given, or "unknown" if not) followed by the manual page
2N/A * section. These are given in `Dt'. The TITLE is a free-form
2N/A * string depending on the manual volume. If not specified, it
2N/A * switches on the manual section.
2N/A */
2N/A
2N/A p->offset = 0;
2N/A p->rmargin = p->maxrmargin;
2N/A
2N/A assert(meta->vol);
2N/A strlcpy(buf, meta->vol, BUFSIZ);
2N/A buflen = term_strlen(p, buf);
2N/A
2N/A if (meta->arch) {
2N/A strlcat(buf, " (", BUFSIZ);
2N/A strlcat(buf, meta->arch, BUFSIZ);
2N/A strlcat(buf, ")", BUFSIZ);
2N/A }
2N/A
2N/A snprintf(title, BUFSIZ, "%s(%s)", meta->title, meta->msec);
2N/A titlen = term_strlen(p, title);
2N/A
2N/A p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
2N/A p->trailspace = 1;
2N/A p->offset = 0;
2N/A p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ?
2N/A (p->maxrmargin -
2N/A term_strlen(p, buf) + term_len(p, 1)) / 2 :
2N/A p->maxrmargin - buflen;
2N/A
2N/A term_word(p, title);
2N/A term_flushln(p);
2N/A
2N/A p->flags |= TERMP_NOSPACE;
2N/A p->offset = p->rmargin;
2N/A p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
2N/A p->maxrmargin - titlen : p->maxrmargin;
2N/A
2N/A term_word(p, buf);
2N/A term_flushln(p);
2N/A
2N/A p->flags &= ~TERMP_NOBREAK;
2N/A p->trailspace = 0;
2N/A if (p->rmargin + titlen <= p->maxrmargin) {
2N/A p->flags |= TERMP_NOSPACE;
2N/A p->offset = p->rmargin;
2N/A p->rmargin = p->maxrmargin;
2N/A term_word(p, title);
2N/A term_flushln(p);
2N/A }
2N/A
2N/A p->flags &= ~TERMP_NOSPACE;
2N/A p->offset = 0;
2N/A p->rmargin = p->maxrmargin;
2N/A}
2N/A
2N/A
2N/Astatic size_t
2N/Aa2height(const struct termp *p, const char *v)
2N/A{
2N/A struct roffsu su;
2N/A
2N/A
2N/A assert(v);
2N/A if ( ! a2roffsu(v, &su, SCALE_VS))
2N/A SCALE_VS_INIT(&su, atoi(v));
2N/A
2N/A return(term_vspan(p, &su));
2N/A}
2N/A
2N/A
2N/Astatic size_t
2N/Aa2width(const struct termp *p, const char *v)
2N/A{
2N/A struct roffsu su;
2N/A
2N/A assert(v);
2N/A if ( ! a2roffsu(v, &su, SCALE_MAX))
2N/A SCALE_HS_INIT(&su, term_strlen(p, v));
2N/A
2N/A return(term_hspan(p, &su));
2N/A}
2N/A
2N/A
2N/Astatic size_t
2N/Aa2offs(const struct termp *p, const char *v)
2N/A{
2N/A struct roffsu su;
2N/A
2N/A if ('\0' == *v)
2N/A return(0);
2N/A else if (0 == strcmp(v, "left"))
2N/A return(0);
2N/A else if (0 == strcmp(v, "indent"))
2N/A return(term_len(p, p->defindent + 1));
2N/A else if (0 == strcmp(v, "indent-two"))
2N/A return(term_len(p, (p->defindent + 1) * 2));
2N/A else if ( ! a2roffsu(v, &su, SCALE_MAX))
2N/A SCALE_HS_INIT(&su, term_strlen(p, v));
2N/A
2N/A return(term_hspan(p, &su));
2N/A}
2N/A
2N/A
2N/A/*
2N/A * Determine how much space to print out before block elements of `It'
2N/A * (and thus `Bl') and `Bd'. And then go ahead and print that space,
2N/A * too.
2N/A */
2N/Astatic void
2N/Aprint_bvspace(struct termp *p,
2N/A const struct mdoc_node *bl,
2N/A const struct mdoc_node *n)
2N/A{
2N/A const struct mdoc_node *nn;
2N/A
2N/A assert(n);
2N/A
2N/A term_newln(p);
2N/A
2N/A if (MDOC_Bd == bl->tok && bl->norm->Bd.comp)
2N/A return;
2N/A if (MDOC_Bl == bl->tok && bl->norm->Bl.comp)
2N/A return;
2N/A
2N/A /* Do not vspace directly after Ss/Sh. */
2N/A
2N/A for (nn = n; nn; nn = nn->parent) {
2N/A if (MDOC_BLOCK != nn->type)
2N/A continue;
2N/A if (MDOC_Ss == nn->tok)
2N/A return;
2N/A if (MDOC_Sh == nn->tok)
2N/A return;
2N/A if (NULL == nn->prev)
2N/A continue;
2N/A break;
2N/A }
2N/A
2N/A /* A `-column' does not assert vspace within the list. */
2N/A
2N/A if (MDOC_Bl == bl->tok && LIST_column == bl->norm->Bl.type)
2N/A if (n->prev && MDOC_It == n->prev->tok)
2N/A return;
2N/A
2N/A /* A `-diag' without body does not vspace. */
2N/A
2N/A if (MDOC_Bl == bl->tok && LIST_diag == bl->norm->Bl.type)
2N/A if (n->prev && MDOC_It == n->prev->tok) {
2N/A assert(n->prev->body);
2N/A if (NULL == n->prev->body->child)
2N/A return;
2N/A }
2N/A
2N/A term_vspace(p);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_it_pre(DECL_ARGS)
2N/A{
2N/A const struct mdoc_node *bl, *nn;
2N/A char buf[7];
2N/A int i;
2N/A size_t width, offset, ncols, dcol;
2N/A enum mdoc_list type;
2N/A
2N/A if (MDOC_BLOCK == n->type) {
2N/A print_bvspace(p, n->parent->parent, n);
2N/A return(1);
2N/A }
2N/A
2N/A bl = n->parent->parent->parent;
2N/A type = bl->norm->Bl.type;
2N/A
2N/A /*
2N/A * First calculate width and offset. This is pretty easy unless
2N/A * we're a -column list, in which case all prior columns must
2N/A * be accounted for.
2N/A */
2N/A
2N/A width = offset = 0;
2N/A
2N/A if (bl->norm->Bl.offs)
2N/A offset = a2offs(p, bl->norm->Bl.offs);
2N/A
2N/A switch (type) {
2N/A case (LIST_column):
2N/A if (MDOC_HEAD == n->type)
2N/A break;
2N/A
2N/A /*
2N/A * Imitate groff's column handling:
2N/A * - For each earlier column, add its width.
2N/A * - For less than 5 columns, add four more blanks per
2N/A * column.
2N/A * - For exactly 5 columns, add three more blank per
2N/A * column.
2N/A * - For more than 5 columns, add only one column.
2N/A */
2N/A ncols = bl->norm->Bl.ncols;
2N/A
2N/A /* LINTED */
2N/A dcol = ncols < 5 ? term_len(p, 4) :
2N/A ncols == 5 ? term_len(p, 3) : term_len(p, 1);
2N/A
2N/A /*
2N/A * Calculate the offset by applying all prior MDOC_BODY,
2N/A * so we stop at the MDOC_HEAD (NULL == nn->prev).
2N/A */
2N/A
2N/A for (i = 0, nn = n->prev;
2N/A nn->prev && i < (int)ncols;
2N/A nn = nn->prev, i++)
2N/A offset += dcol + a2width
2N/A (p, bl->norm->Bl.cols[i]);
2N/A
2N/A /*
2N/A * When exceeding the declared number of columns, leave
2N/A * the remaining widths at 0. This will later be
2N/A * adjusted to the default width of 10, or, for the last
2N/A * column, stretched to the right margin.
2N/A */
2N/A if (i >= (int)ncols)
2N/A break;
2N/A
2N/A /*
2N/A * Use the declared column widths, extended as explained
2N/A * in the preceding paragraph.
2N/A */
2N/A width = a2width(p, bl->norm->Bl.cols[i]) + dcol;
2N/A break;
2N/A default:
2N/A if (NULL == bl->norm->Bl.width)
2N/A break;
2N/A
2N/A /*
2N/A * Note: buffer the width by 2, which is groff's magic
2N/A * number for buffering single arguments. See the above
2N/A * handling for column for how this changes.
2N/A */
2N/A assert(bl->norm->Bl.width);
2N/A width = a2width(p, bl->norm->Bl.width) + term_len(p, 2);
2N/A break;
2N/A }
2N/A
2N/A /*
2N/A * List-type can override the width in the case of fixed-head
2N/A * values (bullet, dash/hyphen, enum). Tags need a non-zero
2N/A * offset.
2N/A */
2N/A
2N/A switch (type) {
2N/A case (LIST_bullet):
2N/A /* FALLTHROUGH */
2N/A case (LIST_dash):
2N/A /* FALLTHROUGH */
2N/A case (LIST_hyphen):
2N/A /* FALLTHROUGH */
2N/A case (LIST_enum):
2N/A if (width < term_len(p, 2))
2N/A width = term_len(p, 2);
2N/A break;
2N/A case (LIST_hang):
2N/A if (0 == width)
2N/A width = term_len(p, 8);
2N/A break;
2N/A case (LIST_column):
2N/A /* FALLTHROUGH */
2N/A case (LIST_tag):
2N/A if (0 == width)
2N/A width = term_len(p, 10);
2N/A break;
2N/A default:
2N/A break;
2N/A }
2N/A
2N/A /*
2N/A * Whitespace control. Inset bodies need an initial space,
2N/A * while diagonal bodies need two.
2N/A */
2N/A
2N/A p->flags |= TERMP_NOSPACE;
2N/A
2N/A switch (type) {
2N/A case (LIST_diag):
2N/A if (MDOC_BODY == n->type)
2N/A term_word(p, "\\ \\ ");
2N/A break;
2N/A case (LIST_inset):
2N/A if (MDOC_BODY == n->type)
2N/A term_word(p, "\\ ");
2N/A break;
2N/A default:
2N/A break;
2N/A }
2N/A
2N/A p->flags |= TERMP_NOSPACE;
2N/A
2N/A switch (type) {
2N/A case (LIST_diag):
2N/A if (MDOC_HEAD == n->type)
2N/A term_fontpush(p, TERMFONT_BOLD);
2N/A break;
2N/A default:
2N/A break;
2N/A }
2N/A
2N/A /*
2N/A * Pad and break control. This is the tricky part. These flags
2N/A * are documented in term_flushln() in term.c. Note that we're
2N/A * going to unset all of these flags in termp_it_post() when we
2N/A * exit.
2N/A */
2N/A
2N/A switch (type) {
2N/A case (LIST_enum):
2N/A /*
2N/A * Weird special case.
2N/A * Very narrow enum lists actually hang.
2N/A */
2N/A if (width == term_len(p, 2))
2N/A p->flags |= TERMP_HANG;
2N/A /* FALLTHROUGH */
2N/A case (LIST_bullet):
2N/A /* FALLTHROUGH */
2N/A case (LIST_dash):
2N/A /* FALLTHROUGH */
2N/A case (LIST_hyphen):
2N/A if (MDOC_HEAD != n->type)
2N/A break;
2N/A p->flags |= TERMP_NOBREAK;
2N/A p->trailspace = 1;
2N/A break;
2N/A case (LIST_hang):
2N/A if (MDOC_HEAD != n->type)
2N/A break;
2N/A
2N/A /*
2N/A * This is ugly. If `-hang' is specified and the body
2N/A * is a `Bl' or `Bd', then we want basically to nullify
2N/A * the "overstep" effect in term_flushln() and treat
2N/A * this as a `-ohang' list instead.
2N/A */
2N/A if (n->next->child &&
2N/A (MDOC_Bl == n->next->child->tok ||
2N/A MDOC_Bd == n->next->child->tok))
2N/A break;
2N/A
2N/A p->flags |= TERMP_NOBREAK | TERMP_HANG;
2N/A p->trailspace = 1;
2N/A break;
2N/A case (LIST_tag):
2N/A if (MDOC_HEAD != n->type)
2N/A break;
2N/A
2N/A p->flags |= TERMP_NOBREAK;
2N/A p->trailspace = 2;
2N/A
2N/A if (NULL == n->next || NULL == n->next->child)
2N/A p->flags |= TERMP_DANGLE;
2N/A break;
2N/A case (LIST_column):
2N/A if (MDOC_HEAD == n->type)
2N/A break;
2N/A
2N/A if (NULL == n->next) {
2N/A p->flags &= ~TERMP_NOBREAK;
2N/A p->trailspace = 0;
2N/A } else {
2N/A p->flags |= TERMP_NOBREAK;
2N/A p->trailspace = 1;
2N/A }
2N/A
2N/A break;
2N/A case (LIST_diag):
2N/A if (MDOC_HEAD != n->type)
2N/A break;
2N/A p->flags |= TERMP_NOBREAK;
2N/A p->trailspace = 1;
2N/A break;
2N/A default:
2N/A break;
2N/A }
2N/A
2N/A /*
2N/A * Margin control. Set-head-width lists have their right
2N/A * margins shortened. The body for these lists has the offset
2N/A * necessarily lengthened. Everybody gets the offset.
2N/A */
2N/A
2N/A p->offset += offset;
2N/A
2N/A switch (type) {
2N/A case (LIST_hang):
2N/A /*
2N/A * Same stipulation as above, regarding `-hang'. We
2N/A * don't want to recalculate rmargin and offsets when
2N/A * using `Bd' or `Bl' within `-hang' overstep lists.
2N/A */
2N/A if (MDOC_HEAD == n->type && n->next->child &&
2N/A (MDOC_Bl == n->next->child->tok ||
2N/A MDOC_Bd == n->next->child->tok))
2N/A break;
2N/A /* FALLTHROUGH */
2N/A case (LIST_bullet):
2N/A /* FALLTHROUGH */
2N/A case (LIST_dash):
2N/A /* FALLTHROUGH */
2N/A case (LIST_enum):
2N/A /* FALLTHROUGH */
2N/A case (LIST_hyphen):
2N/A /* FALLTHROUGH */
2N/A case (LIST_tag):
2N/A assert(width);
2N/A if (MDOC_HEAD == n->type)
2N/A p->rmargin = p->offset + width;
2N/A else
2N/A p->offset += width;
2N/A break;
2N/A case (LIST_column):
2N/A assert(width);
2N/A p->rmargin = p->offset + width;
2N/A /*
2N/A * XXX - this behaviour is not documented: the
2N/A * right-most column is filled to the right margin.
2N/A */
2N/A if (MDOC_HEAD == n->type)
2N/A break;
2N/A if (NULL == n->next && p->rmargin < p->maxrmargin)
2N/A p->rmargin = p->maxrmargin;
2N/A break;
2N/A default:
2N/A break;
2N/A }
2N/A
2N/A /*
2N/A * The dash, hyphen, bullet and enum lists all have a special
2N/A * HEAD character (temporarily bold, in some cases).
2N/A */
2N/A
2N/A if (MDOC_HEAD == n->type)
2N/A switch (type) {
2N/A case (LIST_bullet):
2N/A term_fontpush(p, TERMFONT_BOLD);
2N/A term_word(p, "\\[bu]");
2N/A term_fontpop(p);
2N/A break;
2N/A case (LIST_dash):
2N/A /* FALLTHROUGH */
2N/A case (LIST_hyphen):
2N/A term_fontpush(p, TERMFONT_BOLD);
2N/A term_word(p, "\\(hy");
2N/A term_fontpop(p);
2N/A break;
2N/A case (LIST_enum):
2N/A (pair->ppair->ppair->count)++;
2N/A snprintf(buf, sizeof(buf), "%d.",
2N/A pair->ppair->ppair->count);
2N/A term_word(p, buf);
2N/A break;
2N/A default:
2N/A break;
2N/A }
2N/A
2N/A /*
2N/A * If we're not going to process our children, indicate so here.
2N/A */
2N/A
2N/A switch (type) {
2N/A case (LIST_bullet):
2N/A /* FALLTHROUGH */
2N/A case (LIST_item):
2N/A /* FALLTHROUGH */
2N/A case (LIST_dash):
2N/A /* FALLTHROUGH */
2N/A case (LIST_hyphen):
2N/A /* FALLTHROUGH */
2N/A case (LIST_enum):
2N/A if (MDOC_HEAD == n->type)
2N/A return(0);
2N/A break;
2N/A case (LIST_column):
2N/A if (MDOC_HEAD == n->type)
2N/A return(0);
2N/A break;
2N/A default:
2N/A break;
2N/A }
2N/A
2N/A return(1);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic void
2N/Atermp_it_post(DECL_ARGS)
2N/A{
2N/A enum mdoc_list type;
2N/A
2N/A if (MDOC_BLOCK == n->type)
2N/A return;
2N/A
2N/A type = n->parent->parent->parent->norm->Bl.type;
2N/A
2N/A switch (type) {
2N/A case (LIST_item):
2N/A /* FALLTHROUGH */
2N/A case (LIST_diag):
2N/A /* FALLTHROUGH */
2N/A case (LIST_inset):
2N/A if (MDOC_BODY == n->type)
2N/A term_newln(p);
2N/A break;
2N/A case (LIST_column):
2N/A if (MDOC_BODY == n->type)
2N/A term_flushln(p);
2N/A break;
2N/A default:
2N/A term_newln(p);
2N/A break;
2N/A }
2N/A
2N/A /*
2N/A * Now that our output is flushed, we can reset our tags. Since
2N/A * only `It' sets these flags, we're free to assume that nobody
2N/A * has munged them in the meanwhile.
2N/A */
2N/A
2N/A p->flags &= ~TERMP_DANGLE;
2N/A p->flags &= ~TERMP_NOBREAK;
2N/A p->flags &= ~TERMP_HANG;
2N/A p->trailspace = 0;
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_nm_pre(DECL_ARGS)
2N/A{
2N/A
2N/A if (MDOC_BLOCK == n->type) {
2N/A p->flags |= TERMP_PREKEEP;
2N/A return(1);
2N/A }
2N/A
2N/A if (MDOC_BODY == n->type) {
2N/A if (NULL == n->child)
2N/A return(0);
2N/A p->flags |= TERMP_NOSPACE;
2N/A p->offset += term_len(p, 1) +
2N/A (NULL == n->prev->child ?
2N/A term_strlen(p, meta->name) :
2N/A MDOC_TEXT == n->prev->child->type ?
2N/A term_strlen(p, n->prev->child->string) :
2N/A term_len(p, 5));
2N/A return(1);
2N/A }
2N/A
2N/A if (NULL == n->child && NULL == meta->name)
2N/A return(0);
2N/A
2N/A if (MDOC_HEAD == n->type)
2N/A synopsis_pre(p, n->parent);
2N/A
2N/A if (MDOC_HEAD == n->type && n->next->child) {
2N/A p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
2N/A p->trailspace = 1;
2N/A p->rmargin = p->offset + term_len(p, 1);
2N/A if (NULL == n->child) {
2N/A p->rmargin += term_strlen(p, meta->name);
2N/A } else if (MDOC_TEXT == n->child->type) {
2N/A p->rmargin += term_strlen(p, n->child->string);
2N/A if (n->child->next)
2N/A p->flags |= TERMP_HANG;
2N/A } else {
2N/A p->rmargin += term_len(p, 5);
2N/A p->flags |= TERMP_HANG;
2N/A }
2N/A }
2N/A
2N/A term_fontpush(p, TERMFONT_BOLD);
2N/A if (NULL == n->child)
2N/A term_word(p, meta->name);
2N/A return(1);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic void
2N/Atermp_nm_post(DECL_ARGS)
2N/A{
2N/A
2N/A if (MDOC_BLOCK == n->type) {
2N/A p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
2N/A } else if (MDOC_HEAD == n->type && n->next->child) {
2N/A term_flushln(p);
2N/A p->flags &= ~(TERMP_NOBREAK | TERMP_HANG);
2N/A p->trailspace = 0;
2N/A } else if (MDOC_BODY == n->type && n->child)
2N/A term_flushln(p);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_fl_pre(DECL_ARGS)
2N/A{
2N/A
2N/A term_fontpush(p, TERMFONT_BOLD);
2N/A term_word(p, "\\-");
2N/A
2N/A if (n->child)
2N/A p->flags |= TERMP_NOSPACE;
2N/A else if (n->next && n->next->line == n->line)
2N/A p->flags |= TERMP_NOSPACE;
2N/A
2N/A return(1);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp__a_pre(DECL_ARGS)
2N/A{
2N/A
2N/A if (n->prev && MDOC__A == n->prev->tok)
2N/A if (NULL == n->next || MDOC__A != n->next->tok)
2N/A term_word(p, "and");
2N/A
2N/A return(1);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_an_pre(DECL_ARGS)
2N/A{
2N/A
2N/A if (NULL == n->child)
2N/A return(1);
2N/A
2N/A /*
2N/A * If not in the AUTHORS section, `An -split' will cause
2N/A * newlines to occur before the author name. If in the AUTHORS
2N/A * section, by default, the first `An' invocation is nosplit,
2N/A * then all subsequent ones, regardless of whether interspersed
2N/A * with other macros/text, are split. -split, in this case,
2N/A * will override the condition of the implied first -nosplit.
2N/A */
2N/A
2N/A if (n->sec == SEC_AUTHORS) {
2N/A if ( ! (TERMP_ANPREC & p->flags)) {
2N/A if (TERMP_SPLIT & p->flags)
2N/A term_newln(p);
2N/A return(1);
2N/A }
2N/A if (TERMP_NOSPLIT & p->flags)
2N/A return(1);
2N/A term_newln(p);
2N/A return(1);
2N/A }
2N/A
2N/A if (TERMP_SPLIT & p->flags)
2N/A term_newln(p);
2N/A
2N/A return(1);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic void
2N/Atermp_an_post(DECL_ARGS)
2N/A{
2N/A
2N/A if (n->child) {
2N/A if (SEC_AUTHORS == n->sec)
2N/A p->flags |= TERMP_ANPREC;
2N/A return;
2N/A }
2N/A
2N/A if (AUTH_split == n->norm->An.auth) {
2N/A p->flags &= ~TERMP_NOSPLIT;
2N/A p->flags |= TERMP_SPLIT;
2N/A } else if (AUTH_nosplit == n->norm->An.auth) {
2N/A p->flags &= ~TERMP_SPLIT;
2N/A p->flags |= TERMP_NOSPLIT;
2N/A }
2N/A
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_ns_pre(DECL_ARGS)
2N/A{
2N/A
2N/A if ( ! (MDOC_LINE & n->flags))
2N/A p->flags |= TERMP_NOSPACE;
2N/A return(1);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_rs_pre(DECL_ARGS)
2N/A{
2N/A
2N/A if (SEC_SEE_ALSO != n->sec)
2N/A return(1);
2N/A if (MDOC_BLOCK == n->type && n->prev)
2N/A term_vspace(p);
2N/A return(1);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_rv_pre(DECL_ARGS)
2N/A{
2N/A int nchild;
2N/A
2N/A term_newln(p);
2N/A term_word(p, "The");
2N/A
2N/A nchild = n->nchild;
2N/A for (n = n->child; n; n = n->next) {
2N/A term_fontpush(p, TERMFONT_BOLD);
2N/A term_word(p, n->string);
2N/A term_fontpop(p);
2N/A
2N/A p->flags |= TERMP_NOSPACE;
2N/A term_word(p, "()");
2N/A
2N/A if (nchild > 2 && n->next) {
2N/A p->flags |= TERMP_NOSPACE;
2N/A term_word(p, ",");
2N/A }
2N/A
2N/A if (n->next && NULL == n->next->next)
2N/A term_word(p, "and");
2N/A }
2N/A
2N/A if (nchild > 1)
2N/A term_word(p, "functions return");
2N/A else
2N/A term_word(p, "function returns");
2N/A
2N/A term_word(p, "the value 0 if successful; otherwise the value "
2N/A "-1 is returned and the global variable");
2N/A
2N/A term_fontpush(p, TERMFONT_UNDER);
2N/A term_word(p, "errno");
2N/A term_fontpop(p);
2N/A
2N/A term_word(p, "is set to indicate the error.");
2N/A p->flags |= TERMP_SENTENCE;
2N/A
2N/A return(0);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_ex_pre(DECL_ARGS)
2N/A{
2N/A int nchild;
2N/A
2N/A term_newln(p);
2N/A term_word(p, "The");
2N/A
2N/A nchild = n->nchild;
2N/A for (n = n->child; n; n = n->next) {
2N/A term_fontpush(p, TERMFONT_BOLD);
2N/A term_word(p, n->string);
2N/A term_fontpop(p);
2N/A
2N/A if (nchild > 2 && n->next) {
2N/A p->flags |= TERMP_NOSPACE;
2N/A term_word(p, ",");
2N/A }
2N/A
2N/A if (n->next && NULL == n->next->next)
2N/A term_word(p, "and");
2N/A }
2N/A
2N/A if (nchild > 1)
2N/A term_word(p, "utilities exit");
2N/A else
2N/A term_word(p, "utility exits");
2N/A
2N/A term_word(p, "0 on success, and >0 if an error occurs.");
2N/A
2N/A p->flags |= TERMP_SENTENCE;
2N/A return(0);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_nd_pre(DECL_ARGS)
2N/A{
2N/A
2N/A if (MDOC_BODY != n->type)
2N/A return(1);
2N/A
2N/A#if defined(__OpenBSD__) || defined(__linux__)
2N/A term_word(p, "\\(en");
2N/A#else
2N/A term_word(p, "\\(em");
2N/A#endif
2N/A return(1);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_bl_pre(DECL_ARGS)
2N/A{
2N/A
2N/A return(MDOC_HEAD != n->type);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic void
2N/Atermp_bl_post(DECL_ARGS)
2N/A{
2N/A
2N/A if (MDOC_BLOCK == n->type)
2N/A term_newln(p);
2N/A}
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_xr_pre(DECL_ARGS)
2N/A{
2N/A
2N/A if (NULL == (n = n->child))
2N/A return(0);
2N/A
2N/A assert(MDOC_TEXT == n->type);
2N/A term_word(p, n->string);
2N/A
2N/A if (NULL == (n = n->next))
2N/A return(0);
2N/A
2N/A p->flags |= TERMP_NOSPACE;
2N/A term_word(p, "(");
2N/A p->flags |= TERMP_NOSPACE;
2N/A
2N/A assert(MDOC_TEXT == n->type);
2N/A term_word(p, n->string);
2N/A
2N/A p->flags |= TERMP_NOSPACE;
2N/A term_word(p, ")");
2N/A
2N/A return(0);
2N/A}
2N/A
2N/A/*
2N/A * This decides how to assert whitespace before any of the SYNOPSIS set
2N/A * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain
2N/A * macro combos).
2N/A */
2N/Astatic void
2N/Asynopsis_pre(struct termp *p, const struct mdoc_node *n)
2N/A{
2N/A /*
2N/A * Obviously, if we're not in a SYNOPSIS or no prior macros
2N/A * exist, do nothing.
2N/A */
2N/A if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
2N/A return;
2N/A
2N/A /*
2N/A * If we're the second in a pair of like elements, emit our
2N/A * newline and return. UNLESS we're `Fo', `Fn', `Fn', in which
2N/A * case we soldier on.
2N/A */
2N/A if (n->prev->tok == n->tok &&
2N/A MDOC_Ft != n->tok &&
2N/A MDOC_Fo != n->tok &&
2N/A MDOC_Fn != n->tok) {
2N/A term_newln(p);
2N/A return;
2N/A }
2N/A
2N/A /*
2N/A * If we're one of the SYNOPSIS set and non-like pair-wise after
2N/A * another (or Fn/Fo, which we've let slip through) then assert
2N/A * vertical space, else only newline and move on.
2N/A */
2N/A switch (n->prev->tok) {
2N/A case (MDOC_Fd):
2N/A /* FALLTHROUGH */
2N/A case (MDOC_Fn):
2N/A /* FALLTHROUGH */
2N/A case (MDOC_Fo):
2N/A /* FALLTHROUGH */
2N/A case (MDOC_In):
2N/A /* FALLTHROUGH */
2N/A case (MDOC_Vt):
2N/A term_vspace(p);
2N/A break;
2N/A case (MDOC_Ft):
2N/A if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
2N/A term_vspace(p);
2N/A break;
2N/A }
2N/A /* FALLTHROUGH */
2N/A default:
2N/A term_newln(p);
2N/A break;
2N/A }
2N/A}
2N/A
2N/A
2N/Astatic int
2N/Atermp_vt_pre(DECL_ARGS)
2N/A{
2N/A
2N/A if (MDOC_ELEM == n->type) {
2N/A synopsis_pre(p, n);
2N/A return(termp_under_pre(p, pair, meta, n));
2N/A } else if (MDOC_BLOCK == n->type) {
2N/A synopsis_pre(p, n);
2N/A return(1);
2N/A } else if (MDOC_HEAD == n->type)
2N/A return(0);
2N/A
2N/A return(termp_under_pre(p, pair, meta, n));
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_bold_pre(DECL_ARGS)
2N/A{
2N/A
2N/A term_fontpush(p, TERMFONT_BOLD);
2N/A return(1);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_fd_pre(DECL_ARGS)
2N/A{
2N/A
2N/A synopsis_pre(p, n);
2N/A return(termp_bold_pre(p, pair, meta, n));
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic void
2N/Atermp_fd_post(DECL_ARGS)
2N/A{
2N/A
2N/A term_newln(p);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Atermp_sh_pre(DECL_ARGS)
2N/A{
2N/A
2N/A /* No vspace between consecutive `Sh' calls. */
2N/A
2N/A switch (n->type) {
2N/A case (MDOC_BLOCK):
2N/A if (n->prev && MDOC_Sh == n->prev->tok)
2N/A if (NULL == n->prev->body->child)
2N/A break;
2N/A term_vspace(p);
2N/A break;
2N/A case (MDOC_HEAD):
2N/A term_fontpush(p, TERMFONT_BOLD);
2N/A break;
2N/A case (MDOC_BODY):
2N/A p->offset = term_len(p, p->defindent);
2N/A if (SEC_AUTHORS == n->sec)
2N/A p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT);
2N/A break;
default:
break;
}
return(1);
}
/* ARGSUSED */
static void
termp_sh_post(DECL_ARGS)
{
switch (n->type) {
case (MDOC_HEAD):
term_newln(p);
break;
case (MDOC_BODY):
term_newln(p);
p->offset = 0;
break;
default:
break;
}
}
/* ARGSUSED */
static int
termp_bt_pre(DECL_ARGS)
{
term_word(p, "is currently in beta test.");
p->flags |= TERMP_SENTENCE;
return(0);
}
/* ARGSUSED */
static void
termp_lb_post(DECL_ARGS)
{
if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags)
term_newln(p);
}
/* ARGSUSED */
static int
termp_ud_pre(DECL_ARGS)
{
term_word(p, "currently under development.");
p->flags |= TERMP_SENTENCE;
return(0);
}
/* ARGSUSED */
static int
termp_d1_pre(DECL_ARGS)
{
if (MDOC_BLOCK != n->type)
return(1);
term_newln(p);
p->offset += term_len(p, p->defindent + 1);
return(1);
}
/* ARGSUSED */
static int
termp_ft_pre(DECL_ARGS)
{
/* NB: MDOC_LINE does not effect this! */
synopsis_pre(p, n);
term_fontpush(p, TERMFONT_UNDER);
return(1);
}
/* ARGSUSED */
static int
termp_fn_pre(DECL_ARGS)
{
size_t rmargin = 0;
int pretty;
pretty = MDOC_SYNPRETTY & n->flags;
synopsis_pre(p, n);
if (NULL == (n = n->child))
return(0);
if (pretty) {
rmargin = p->rmargin;
p->rmargin = p->offset + term_len(p, 4);
p->flags |= TERMP_NOBREAK | TERMP_HANG;
}
assert(MDOC_TEXT == n->type);
term_fontpush(p, TERMFONT_BOLD);
term_word(p, n->string);
term_fontpop(p);
if (pretty) {
term_flushln(p);
p->flags &= ~(TERMP_NOBREAK | TERMP_HANG);
p->offset = p->rmargin;
p->rmargin = rmargin;
}
p->flags |= TERMP_NOSPACE;
term_word(p, "(");
p->flags |= TERMP_NOSPACE;
for (n = n->next; n; n = n->next) {
assert(MDOC_TEXT == n->type);
term_fontpush(p, TERMFONT_UNDER);
if (pretty)
p->flags |= TERMP_NBRWORD;
term_word(p, n->string);
term_fontpop(p);
if (n->next) {
p->flags |= TERMP_NOSPACE;
term_word(p, ",");
}
}
p->flags |= TERMP_NOSPACE;
term_word(p, ")");
if (pretty) {
p->flags |= TERMP_NOSPACE;
term_word(p, ";");
term_flushln(p);
}
return(0);
}
/* ARGSUSED */
static int
termp_fa_pre(DECL_ARGS)
{
const struct mdoc_node *nn;
if (n->parent->tok != MDOC_Fo) {
term_fontpush(p, TERMFONT_UNDER);
return(1);
}
for (nn = n->child; nn; nn = nn->next) {
term_fontpush(p, TERMFONT_UNDER);
p->flags |= TERMP_NBRWORD;
term_word(p, nn->string);
term_fontpop(p);
if (nn->next || (n->next && n->next->tok == MDOC_Fa)) {
p->flags |= TERMP_NOSPACE;
term_word(p, ",");
}
}
return(0);
}
/* ARGSUSED */
static int
termp_bd_pre(DECL_ARGS)
{
size_t tabwidth, rm, rmax;
struct mdoc_node *nn;
if (MDOC_BLOCK == n->type) {
print_bvspace(p, n, n);
return(1);
} else if (MDOC_HEAD == n->type)
return(0);
if (n->norm->Bd.offs)
p->offset += a2offs(p, n->norm->Bd.offs);
/*
* If -ragged or -filled are specified, the block does nothing
* but change the indentation. If -unfilled or -literal are
* specified, text is printed exactly as entered in the display:
* for macro lines, a newline is appended to the line. Blank
* lines are allowed.
*/
if (DISP_literal != n->norm->Bd.type &&
DISP_unfilled != n->norm->Bd.type)
return(1);
tabwidth = p->tabwidth;
if (DISP_literal == n->norm->Bd.type)
p->tabwidth = term_len(p, 8);
rm = p->rmargin;
rmax = p->maxrmargin;
p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
for (nn = n->child; nn; nn = nn->next) {
print_mdoc_node(p, pair, meta, nn);
/*
* If the printed node flushes its own line, then we
* needn't do it here as well. This is hacky, but the
* notion of selective eoln whitespace is pretty dumb
* anyway, so don't sweat it.
*/
switch (nn->tok) {
case (MDOC_Sm):
/* FALLTHROUGH */
case (MDOC_br):
/* FALLTHROUGH */
case (MDOC_sp):
/* FALLTHROUGH */
case (MDOC_Bl):
/* FALLTHROUGH */
case (MDOC_D1):
/* FALLTHROUGH */
case (MDOC_Dl):
/* FALLTHROUGH */
case (MDOC_Lp):
/* FALLTHROUGH */
case (MDOC_Pp):
continue;
default:
break;
}
if (nn->next && nn->next->line == nn->line)
continue;
term_flushln(p);
p->flags |= TERMP_NOSPACE;
}
p->tabwidth = tabwidth;
p->rmargin = rm;
p->maxrmargin = rmax;
return(0);
}
/* ARGSUSED */
static void
termp_bd_post(DECL_ARGS)
{
size_t rm, rmax;
if (MDOC_BODY != n->type)
return;
rm = p->rmargin;
rmax = p->maxrmargin;
if (DISP_literal == n->norm->Bd.type ||
DISP_unfilled == n->norm->Bd.type)
p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
p->flags |= TERMP_NOSPACE;
term_newln(p);
p->rmargin = rm;
p->maxrmargin = rmax;
}
/* ARGSUSED */
static int
termp_bx_pre(DECL_ARGS)
{
if (NULL != (n = n->child)) {
term_word(p, n->string);
p->flags |= TERMP_NOSPACE;
term_word(p, "BSD");
} else {
term_word(p, "BSD");
return(0);
}
if (NULL != (n = n->next)) {
p->flags |= TERMP_NOSPACE;
term_word(p, "-");
p->flags |= TERMP_NOSPACE;
term_word(p, n->string);
}
return(0);
}
/* ARGSUSED */
static int
termp_xx_pre(DECL_ARGS)
{
const char *pp;
int flags;
pp = NULL;
switch (n->tok) {
case (MDOC_Bsx):
pp = "BSD/OS";
break;
case (MDOC_Dx):
pp = "DragonFly";
break;
case (MDOC_Fx):
pp = "FreeBSD";
break;
case (MDOC_Nx):
pp = "NetBSD";
break;
case (MDOC_Ox):
pp = "OpenBSD";
break;
case (MDOC_Ux):
pp = "UNIX";
break;
default:
abort();
/* NOTREACHED */
}
term_word(p, pp);
if (n->child) {
flags = p->flags;
p->flags |= TERMP_KEEP;
term_word(p, n->child->string);
p->flags = flags;
}
return(0);
}
/* ARGSUSED */
static void
termp_pf_post(DECL_ARGS)
{
p->flags |= TERMP_NOSPACE;
}
/* ARGSUSED */
static int
termp_ss_pre(DECL_ARGS)
{
switch (n->type) {
case (MDOC_BLOCK):
term_newln(p);
if (n->prev)
term_vspace(p);
break;
case (MDOC_HEAD):
term_fontpush(p, TERMFONT_BOLD);
p->offset = term_len(p, (p->defindent+1)/2);
break;
default:
break;
}
return(1);
}
/* ARGSUSED */
static void
termp_ss_post(DECL_ARGS)
{
if (MDOC_HEAD == n->type)
term_newln(p);
}
/* ARGSUSED */
static int
termp_cd_pre(DECL_ARGS)
{
synopsis_pre(p, n);
term_fontpush(p, TERMFONT_BOLD);
return(1);
}
/* ARGSUSED */
static int
termp_in_pre(DECL_ARGS)
{
synopsis_pre(p, n);
if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) {
term_fontpush(p, TERMFONT_BOLD);
term_word(p, "#include");
term_word(p, "<");
} else {
term_word(p, "<");
term_fontpush(p, TERMFONT_UNDER);
}
p->flags |= TERMP_NOSPACE;
return(1);
}
/* ARGSUSED */
static void
termp_in_post(DECL_ARGS)
{
if (MDOC_SYNPRETTY & n->flags)
term_fontpush(p, TERMFONT_BOLD);
p->flags |= TERMP_NOSPACE;
term_word(p, ">");
if (MDOC_SYNPRETTY & n->flags)
term_fontpop(p);
}
/* ARGSUSED */
static int
termp_sp_pre(DECL_ARGS)
{
size_t i, len;
switch (n->tok) {
case (MDOC_sp):
len = n->child ? a2height(p, n->child->string) : 1;
break;
case (MDOC_br):
len = 0;
break;
default:
len = 1;
break;
}
if (0 == len)
term_newln(p);
for (i = 0; i < len; i++)
term_vspace(p);
return(0);
}
/* ARGSUSED */
static int
termp_quote_pre(DECL_ARGS)
{
if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
return(1);
switch (n->tok) {
case (MDOC_Ao):
/* FALLTHROUGH */
case (MDOC_Aq):
term_word(p, "<");
break;
case (MDOC_Bro):
/* FALLTHROUGH */
case (MDOC_Brq):
term_word(p, "{");
break;
case (MDOC_Oo):
/* FALLTHROUGH */
case (MDOC_Op):
/* FALLTHROUGH */
case (MDOC_Bo):
/* FALLTHROUGH */
case (MDOC_Bq):
term_word(p, "[");
break;
case (MDOC_Do):
/* FALLTHROUGH */
case (MDOC_Dq):
term_word(p, "\\(lq");
break;
case (MDOC_Eo):
break;
case (MDOC_Po):
/* FALLTHROUGH */
case (MDOC_Pq):
term_word(p, "(");
break;
case (MDOC__T):
/* FALLTHROUGH */
case (MDOC_Qo):
/* FALLTHROUGH */
case (MDOC_Qq):
term_word(p, "\"");
break;
case (MDOC_Ql):
/* FALLTHROUGH */
case (MDOC_So):
/* FALLTHROUGH */
case (MDOC_Sq):
term_word(p, "\\(oq");
break;
default:
abort();
/* NOTREACHED */
}
p->flags |= TERMP_NOSPACE;
return(1);
}
/* ARGSUSED */
static void
termp_quote_post(DECL_ARGS)
{
if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
return;
p->flags |= TERMP_NOSPACE;
switch (n->tok) {
case (MDOC_Ao):
/* FALLTHROUGH */
case (MDOC_Aq):
term_word(p, ">");
break;
case (MDOC_Bro):
/* FALLTHROUGH */
case (MDOC_Brq):
term_word(p, "}");
break;
case (MDOC_Oo):
/* FALLTHROUGH */
case (MDOC_Op):
/* FALLTHROUGH */
case (MDOC_Bo):
/* FALLTHROUGH */
case (MDOC_Bq):
term_word(p, "]");
break;
case (MDOC_Do):
/* FALLTHROUGH */
case (MDOC_Dq):
term_word(p, "\\(rq");
break;
case (MDOC_Eo):
break;
case (MDOC_Po):
/* FALLTHROUGH */
case (MDOC_Pq):
term_word(p, ")");
break;
case (MDOC__T):
/* FALLTHROUGH */
case (MDOC_Qo):
/* FALLTHROUGH */
case (MDOC_Qq):
term_word(p, "\"");
break;
case (MDOC_Ql):
/* FALLTHROUGH */
case (MDOC_So):
/* FALLTHROUGH */
case (MDOC_Sq):
term_word(p, "\\(cq");
break;
default:
abort();
/* NOTREACHED */
}
}
/* ARGSUSED */
static int
termp_fo_pre(DECL_ARGS)
{
size_t rmargin = 0;
int pretty;
pretty = MDOC_SYNPRETTY & n->flags;
if (MDOC_BLOCK == n->type) {
synopsis_pre(p, n);
return(1);
} else if (MDOC_BODY == n->type) {
if (pretty) {
rmargin = p->rmargin;
p->rmargin = p->offset + term_len(p, 4);
p->flags |= TERMP_NOBREAK | TERMP_HANG;
}
p->flags |= TERMP_NOSPACE;
term_word(p, "(");
p->flags |= TERMP_NOSPACE;
if (pretty) {
term_flushln(p);
p->flags &= ~(TERMP_NOBREAK | TERMP_HANG);
p->offset = p->rmargin;
p->rmargin = rmargin;
}
return(1);
}
if (NULL == n->child)
return(0);
/* XXX: we drop non-initial arguments as per groff. */
assert(n->child->string);
term_fontpush(p, TERMFONT_BOLD);
term_word(p, n->child->string);
return(0);
}
/* ARGSUSED */
static void
termp_fo_post(DECL_ARGS)
{
if (MDOC_BODY != n->type)
return;
p->flags |= TERMP_NOSPACE;
term_word(p, ")");
if (MDOC_SYNPRETTY & n->flags) {
p->flags |= TERMP_NOSPACE;
term_word(p, ";");
term_flushln(p);
}
}
/* ARGSUSED */
static int
termp_bf_pre(DECL_ARGS)
{
if (MDOC_HEAD == n->type)
return(0);
else if (MDOC_BODY != n->type)
return(1);
if (FONT_Em == n->norm->Bf.font)
term_fontpush(p, TERMFONT_UNDER);
else if (FONT_Sy == n->norm->Bf.font)
term_fontpush(p, TERMFONT_BOLD);
else
term_fontpush(p, TERMFONT_NONE);
return(1);
}
/* ARGSUSED */
static int
termp_sm_pre(DECL_ARGS)
{
assert(n->child && MDOC_TEXT == n->child->type);
if (0 == strcmp("on", n->child->string)) {
if (p->col)
p->flags &= ~TERMP_NOSPACE;
p->flags &= ~TERMP_NONOSPACE;
} else
p->flags |= TERMP_NONOSPACE;
return(0);
}
/* ARGSUSED */
static int
termp_ap_pre(DECL_ARGS)
{
p->flags |= TERMP_NOSPACE;
term_word(p, "'");
p->flags |= TERMP_NOSPACE;
return(1);
}
/* ARGSUSED */
static void
termp____post(DECL_ARGS)
{
/*
* Handle lists of authors. In general, print each followed by
* a comma. Don't print the comma if there are only two
* authors.
*/
if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
if (NULL == n->next->next || MDOC__A != n->next->next->tok)
if (NULL == n->prev || MDOC__A != n->prev->tok)
return;
/* TODO: %U. */
if (NULL == n->parent || MDOC_Rs != n->parent->tok)
return;
p->flags |= TERMP_NOSPACE;
if (NULL == n->next) {
term_word(p, ".");
p->flags |= TERMP_SENTENCE;
} else
term_word(p, ",");
}
/* ARGSUSED */
static int
termp_li_pre(DECL_ARGS)
{
term_fontpush(p, TERMFONT_NONE);
return(1);
}
/* ARGSUSED */
static int
termp_lk_pre(DECL_ARGS)
{
const struct mdoc_node *link, *descr;
if (NULL == (link = n->child))
return(0);
if (NULL != (descr = link->next)) {
term_fontpush(p, TERMFONT_UNDER);
while (NULL != descr) {
term_word(p, descr->string);
descr = descr->next;
}
p->flags |= TERMP_NOSPACE;
term_word(p, ":");
term_fontpop(p);
}
term_fontpush(p, TERMFONT_BOLD);
term_word(p, link->string);
term_fontpop(p);
return(0);
}
/* ARGSUSED */
static int
termp_bk_pre(DECL_ARGS)
{
switch (n->type) {
case (MDOC_BLOCK):
break;
case (MDOC_HEAD):
return(0);
case (MDOC_BODY):
if (n->parent->args || 0 == n->prev->nchild)
p->flags |= TERMP_PREKEEP;
break;
default:
abort();
/* NOTREACHED */
}
return(1);
}
/* ARGSUSED */
static void
termp_bk_post(DECL_ARGS)
{
if (MDOC_BODY == n->type)
p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
}
/* ARGSUSED */
static void
termp__t_post(DECL_ARGS)
{
/*
* If we're in an `Rs' and there's a journal present, then quote
* us instead of underlining us (for disambiguation).
*/
if (n->parent && MDOC_Rs == n->parent->tok &&
n->parent->norm->Rs.quote_T)
termp_quote_post(p, pair, meta, n);
termp____post(p, pair, meta, n);
}
/* ARGSUSED */
static int
termp__t_pre(DECL_ARGS)
{
/*
* If we're in an `Rs' and there's a journal present, then quote
* us instead of underlining us (for disambiguation).
*/
if (n->parent && MDOC_Rs == n->parent->tok &&
n->parent->norm->Rs.quote_T)
return(termp_quote_pre(p, pair, meta, n));
term_fontpush(p, TERMFONT_UNDER);
return(1);
}
/* ARGSUSED */
static int
termp_under_pre(DECL_ARGS)
{
term_fontpush(p, TERMFONT_UNDER);
return(1);
}