-/* $Id: mdoc_term.c,v 1.39 2009/07/14 15:17:25 kristaps Exp $ */
+/* $Id: mdoc_term.c,v 1.66 2009/08/10 10:09:51 kristaps Exp $ */
/*
* Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
*
#include "term.h"
#include "mdoc.h"
+#define INDENT 5
+#define HALFINDENT 3
+
/* FIXME: macro arguments can be escaped. */
-/* FIXME: support more offset/width tokens. */
#define TTYPE_PROG 0
#define TTYPE_CMD_FLAG 1
#define TTYPE_LINK_ANCHOR 19
#define TTYPE_LINK_TEXT 20
#define TTYPE_REF_JOURNAL 21
-#define TTYPE_LIST 22
-#define TTYPE_NMAX 23
+#define TTYPE_REF_TITLE 22
+#define TTYPE_LIST 23
+#define TTYPE_NMAX 24
const int ttypes[TTYPE_NMAX] = {
TERMP_BOLD, /* TTYPE_PROG */
TERMP_UNDER, /* TTYPE_FUNC_ARG */
TERMP_UNDER, /* TTYPE_LINK */
TERMP_BOLD, /* TTYPE_SSECTION */
- TERMP_UNDER, /* TTYPE_FILE */
+ TERMP_UNDER, /* TTYPE_FILE */
TERMP_UNDER, /* TTYPE_EMPH */
TERMP_BOLD, /* TTYPE_CONFIG */
TERMP_BOLD, /* TTYPE_CMD */
TERMP_UNDER, /* TTYPE_LINK_ANCHOR */
TERMP_BOLD, /* TTYPE_LINK_TEXT */
TERMP_UNDER, /* TTYPE_REF_JOURNAL */
+ TERMP_UNDER, /* TTYPE_REF_TITLE */
TERMP_BOLD /* TTYPE_LIST */
};
};
static void termp____post(DECL_ARGS);
-static void termp__t_post(DECL_ARGS);
+static void termp_an_post(DECL_ARGS);
static void termp_aq_post(DECL_ARGS);
static void termp_bd_post(DECL_ARGS);
static void termp_bl_post(DECL_ARGS);
static int termp__j_pre(DECL_ARGS);
static int termp__t_pre(DECL_ARGS);
+static int termp_an_pre(DECL_ARGS);
static int termp_ap_pre(DECL_ARGS);
static int termp_aq_pre(DECL_ARGS);
static int termp_ar_pre(DECL_ARGS);
static int termp_bd_pre(DECL_ARGS);
static int termp_bf_pre(DECL_ARGS);
static int termp_bq_pre(DECL_ARGS);
+static int termp_br_pre(DECL_ARGS);
static int termp_brq_pre(DECL_ARGS);
static int termp_bt_pre(DECL_ARGS);
static int termp_cd_pre(DECL_ARGS);
static int termp_rv_pre(DECL_ARGS);
static int termp_sh_pre(DECL_ARGS);
static int termp_sm_pre(DECL_ARGS);
+static int termp_sp_pre(DECL_ARGS);
static int termp_sq_pre(DECL_ARGS);
static int termp_ss_pre(DECL_ARGS);
static int termp_sx_pre(DECL_ARGS);
{ NULL, NULL }, /* El */
{ termp_it_pre, termp_it_post }, /* It */
{ NULL, NULL }, /* Ad */
- { NULL, NULL }, /* An */
+ { termp_an_pre, termp_an_post }, /* An */
{ termp_ar_pre, NULL }, /* Ar */
{ termp_cd_pre, NULL }, /* Cd */
{ termp_cm_pre, NULL }, /* Cm */
{ NULL, termp____post }, /* %O */
{ NULL, termp____post }, /* %P */
{ NULL, termp____post }, /* %R */
- { termp__t_pre, termp__t_post }, /* %T */
+ { termp__t_pre, termp____post }, /* %T */
{ NULL, termp____post }, /* %V */
{ NULL, NULL }, /* Ac */
{ termp_aq_pre, termp_aq_post }, /* Ao */
{ NULL, NULL }, /* En */
{ termp_xx_pre, NULL }, /* Dx */
{ NULL, NULL }, /* %Q */
+ { termp_br_pre, NULL }, /* br */
+ { termp_sp_pre, NULL }, /* sp */
};
#ifdef __linux__
static size_t arg_offset(const struct mdoc_argv *);
static size_t arg_width(const struct mdoc_argv *, int);
static int arg_listtype(const struct mdoc_node *);
-static int fmt_block_vspace(struct termp *,
+static void fmt_block_vspace(struct termp *,
const struct mdoc_node *,
const struct mdoc_node *);
static void print_node(DECL_ARGS);
npair.flag = 0;
npair.count = 0;
+ /*
+ * Note on termpair. This allows a pre function to set a termp
+ * flag that is automatically unset after the body, but before
+ * the post function. Thus, if a pre uses a termpair flag, it
+ * must be reapplied in the post for use.
+ */
+
if (MDOC_TEXT != node->type) {
if (termacts[node->tok].pre)
if ( ! (*termacts[node->tok].pre)(p, &npair, meta, node))
if (dochild && node->child)
print_body(p, &npair, meta, node->child);
+ p->flags &= ~npair.flag;
+
/* Post-processing. */
if (MDOC_TEXT != node->type)
p->offset = offset;
p->rmargin = rmargin;
- p->flags &= ~npair.flag;
}
tm = localtime(&meta->date);
- if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
+ if (0 == strftime(buf, p->rmargin, "%B %e, %Y", tm))
err(1, "strftime");
(void)strlcpy(os, meta->os, p->rmargin);
static size_t
arg_width(const struct mdoc_argv *arg, int pos)
{
- size_t v;
int i, len;
+ const char *p;
assert(pos < (int)arg->sz && pos >= 0);
assert(arg->value[pos]);
- if (0 == (len = (int)strlen(arg->value[pos])))
+ p = arg->value[pos];
+
+ if (0 == (len = (int)strlen(p)))
return(0);
for (i = 0; i < len - 1; i++)
- if ( ! isdigit((u_char)arg->value[pos][i]))
+ if ( ! isdigit((u_char)p[i]))
break;
- if (i == len - 1) {
- if ('n' == arg->value[pos][len - 1] ||
- 'm' == arg->value[pos][len - 1]) {
- v = (size_t)atoi(arg->value[pos]);
- return(v + 2);
- }
+ if (i == len - 1)
+ if ('n' == p[len - 1] || 'm' == p[len - 1])
+ return((size_t)atoi(p) + 2);
- }
- return(strlen(arg->value[pos]) + 2);
+ return((size_t)len + 2);
}
static size_t
arg_offset(const struct mdoc_argv *arg)
{
+ int len, i;
+ const char *p;
assert(*arg->value);
- if (0 == strcmp(*arg->value, "left"))
+ p = *arg->value;
+
+ if (0 == strcmp(p, "left"))
return(0);
- if (0 == strcmp(*arg->value, "indent"))
+ if (0 == strcmp(p, "indent"))
return(INDENT + 1);
- if (0 == strcmp(*arg->value, "indent-two"))
+ if (0 == strcmp(p, "indent-two"))
return((INDENT + 1) * 2);
- /* FIXME: needs to support field-widths (10n, etc.). */
+ if (0 == (len = (int)strlen(p)))
+ return(0);
- return(strlen(*arg->value));
+ for (i = 0; i < len - 1; i++)
+ if ( ! isdigit((u_char)p[i]))
+ break;
+
+ if (i == len - 1)
+ if ('n' == p[len - 1] || 'm' == p[len - 1])
+ return((size_t)atoi(p));
+
+ return((size_t)len);
}
/* ARGSUSED */
-static int
+static void
fmt_block_vspace(struct termp *p,
const struct mdoc_node *bl,
const struct mdoc_node *node)
term_newln(p);
- if (arg_hasattr(MDOC_Compact, bl))
- return(1);
+ if (MDOC_Bl == bl->tok && arg_hasattr(MDOC_Compact, bl))
+ return;
+ assert(node);
+
+ /*
+ * Search through our prior nodes. If we follow a `Ss' or `Sh',
+ * then don't vspace.
+ */
for (n = node; n; n = n->parent) {
if (MDOC_BLOCK != n->type)
continue;
if (MDOC_Ss == n->tok)
- break;
+ return;
if (MDOC_Sh == n->tok)
- break;
+ return;
if (NULL == n->prev)
continue;
- term_vspace(p);
break;
}
- return(1);
+ /*
+ * XXX - not documented: a `-column' does not ever assert vspace
+ * within the list.
+ */
+
+ if (MDOC_Bl == bl->tok && arg_hasattr(MDOC_Column, bl))
+ if (node->prev && MDOC_It == node->prev->tok)
+ return;
+
+ /*
+ * XXX - not documented: a `-diag' without a body does not
+ * assert a vspace prior to the next element.
+ */
+ if (MDOC_Bl == bl->tok && arg_hasattr(MDOC_Diag, bl))
+ if (node->prev && MDOC_It == node->prev->tok) {
+ assert(node->prev->body);
+ if (NULL == node->prev->body->child)
+ return;
+ }
+
+ term_vspace(p);
}
int i, type, keys[3], vals[3];
size_t width, offset;
- if (MDOC_BLOCK == node->type)
- return(fmt_block_vspace(p, node->parent->parent, node));
+ if (MDOC_BLOCK == node->type) {
+ fmt_block_vspace(p, node->parent->parent, node);
+ return(1);
+ }
bl = node->parent->parent->parent;
case (MDOC_Column):
if (MDOC_BODY == node->type)
break;
- for (i = 0, n = node->prev; n; n = n->prev, i++)
+ /*
+ * Work around groff's column handling. The offset is
+ * equal to the sum of all widths leading to the current
+ * column (plus the -offset value). If this column
+ * exceeds the stated number of columns, the width is
+ * set as 0, else it's the stated column width (later
+ * the 0 will be adjusted to default 10 or, if in the
+ * last column case, set to stretch to the margin).
+ */
+ for (i = 0, n = node->prev; n && n &&
+ i < (int)bl->args[vals[2]].argv->sz;
+ n = n->prev, i++)
offset += arg_width
(&bl->args->argv[vals[2]], i);
- assert(i < (int)bl->args->argv[vals[2]].sz);
- width = arg_width(&bl->args->argv[vals[2]], i);
+
+ /* Whether exceeds maximum column. */
+ if (i < (int)bl->args[vals[2]].argv->sz)
+ width = arg_width(&bl->args->argv[vals[2]], i);
+ else
+ width = 0;
+
if (vals[1] >= 0)
offset += arg_offset(&bl->args->argv[vals[1]]);
break;
if (0 == width)
width = 8;
break;
+ case (MDOC_Column):
+ /* FALLTHROUGH */
case (MDOC_Tag):
if (0 == width)
width = 10;
* while diagonal bodies need two.
*/
+ p->flags |= TERMP_NOSPACE;
+
switch (type) {
case (MDOC_Diag):
- term_word(p, "\\ ");
- /* FALLTHROUGH */
+ if (MDOC_BODY == node->type)
+ term_word(p, "\\ \\ ");
+ break;
case (MDOC_Inset):
if (MDOC_BODY == node->type)
- p->flags &= ~TERMP_NOSPACE;
- else
- p->flags |= TERMP_NOSPACE;
+ term_word(p, "\\ ");
break;
default:
- p->flags |= TERMP_NOSPACE;
break;
}
+ p->flags |= TERMP_NOSPACE;
+
/*
* Style flags. Diagnostic heads need TTYPE_DIAG.
*/
else
p->flags |= TERMP_NOLPAD;
- if (MDOC_HEAD == node->type)
+ if (MDOC_HEAD != node->type)
+ break;
+
+ /*
+ * This is ugly. If `-hang' is specified and the body
+ * is a `Bl' or `Bd', then we want basically to nullify
+ * the "overstep" effect in term_flushln() and treat
+ * this as a `-ohang' list instead.
+ */
+ if (node->next->child &&
+ (MDOC_Bl == node->next->child->tok ||
+ MDOC_Bd == node->next->child->tok)) {
+ p->flags &= ~TERMP_NOBREAK;
+ p->flags &= ~TERMP_NOLPAD;
+ } else
p->flags |= TERMP_HANG;
break;
case (MDOC_Tag):
if (MDOC_HEAD == node->type)
- p->flags |= TERMP_NOBREAK;
+ p->flags |= TERMP_NOBREAK | TERMP_TWOSPACE;
else
p->flags |= TERMP_NOLPAD;
p->offset += offset;
switch (type) {
+ case (MDOC_Hang):
+ /*
+ * Same stipulation as above, regarding `-hang'. We
+ * don't want to recalculate rmargin and offsets when
+ * using `Bd' or `Bl' within `-hang' overstep lists.
+ */
+ if (MDOC_HEAD == node->type && node->next->child &&
+ (MDOC_Bl == node->next->child->tok ||
+ MDOC_Bd == node->next->child->tok))
+ break;
+ /* FALLTHROUGH */
case (MDOC_Bullet):
/* FALLTHROUGH */
case (MDOC_Dash):
/* FALLTHROUGH */
case (MDOC_Hyphen):
/* FALLTHROUGH */
- case (MDOC_Hang):
- /* FALLTHROUGH */
case (MDOC_Tag):
+ assert(width);
if (MDOC_HEAD == node->type)
p->rmargin = p->offset + width;
else
p->offset += width;
break;
case (MDOC_Column):
+ assert(width);
p->rmargin = p->offset + width;
+ /*
+ * XXX - this behaviour is not documented: the
+ * right-most column is filled to the right margin.
+ */
+ if (MDOC_HEAD == node->type &&
+ MDOC_BODY == node->next->type)
+ p->rmargin = p->maxrmargin;
break;
default:
break;
assert(-1 != type);
switch (type) {
- case (MDOC_Diag):
- /* FALLTHROUGH */
case (MDOC_Item):
/* FALLTHROUGH */
+ case (MDOC_Diag):
+ /* FALLTHROUGH */
case (MDOC_Inset):
if (MDOC_BODY == node->type)
term_flushln(p);
}
+/* ARGSUSED */
+static int
+termp_an_pre(DECL_ARGS)
+{
+
+ if (NULL == node->child)
+ return(1);
+
+ /*
+ * XXX: this is poorly documented. If not in the AUTHORS
+ * section, `An -split' will cause newlines to occur before the
+ * author name. If in the AUTHORS section, by default, the
+ * first `An' invocation is nosplit, then all subsequent ones,
+ * regardless of whether interspersed with other macros/text,
+ * are split. -split, in this case, will override the condition
+ * of the implied first -nosplit.
+ */
+
+ if (node->sec == SEC_AUTHORS) {
+ if ( ! (TERMP_ANPREC & p->flags)) {
+ if (TERMP_SPLIT & p->flags)
+ term_newln(p);
+ return(1);
+ }
+ if (TERMP_NOSPLIT & p->flags)
+ return(1);
+ term_newln(p);
+ return(1);
+ }
+
+ if (TERMP_SPLIT & p->flags)
+ term_newln(p);
+
+ return(1);
+}
+
+
+/* ARGSUSED */
+static void
+termp_an_post(DECL_ARGS)
+{
+
+ if (node->child) {
+ if (SEC_AUTHORS == node->sec)
+ p->flags |= TERMP_ANPREC;
+ return;
+ }
+
+ if (arg_getattr(MDOC_Split, node) > -1) {
+ p->flags &= ~TERMP_NOSPLIT;
+ p->flags |= TERMP_SPLIT;
+ } else {
+ p->flags &= ~TERMP_SPLIT;
+ p->flags |= TERMP_NOSPLIT;
+ }
+
+}
+
+
/* ARGSUSED */
static int
termp_ar_pre(DECL_ARGS)
if (MDOC_BODY != node->type)
return(1);
- /*
- * XXX: signed off by jmc@openbsd.org. This technically
- * produces a minus sign after the Nd, which is wrong, but is
- * consistent with the historic OpenBSD tmac file.
- */
#if defined(__OpenBSD__) || defined(__linux__)
- term_word(p, "\\-");
+ term_word(p, "\\(en");
#else
term_word(p, "\\(em");
#endif
static int
termp_sh_pre(DECL_ARGS)
{
-
+ /*
+ * XXX: undocumented: using two `Sh' macros in sequence has no
+ * vspace between calls, only a newline.
+ */
switch (node->type) {
- case (MDOC_HEAD):
+ case (MDOC_BLOCK):
+ if (node->prev && MDOC_Sh == node->prev->tok)
+ if (NULL == node->prev->body->child)
+ break;
term_vspace(p);
+ break;
+ case (MDOC_HEAD):
pair->flag |= ttypes[TTYPE_SECTION];
break;
case (MDOC_BODY):
static int
termp_bd_pre(DECL_ARGS)
{
- int i, type, ln;
+ int i, type;
/*
* This is fairly tricky due primarily to crappy documentation.
* line. Blank lines are allowed.
*/
- if (MDOC_BLOCK == node->type)
- return(fmt_block_vspace(p, node, node));
- else if (MDOC_BODY != node->type)
+ if (MDOC_BLOCK == node->type) {
+ fmt_block_vspace(p, node, node);
+ return(1);
+ } else if (MDOC_BODY != node->type)
return(1);
- /* FIXME: display type should be mandated by parser. */
-
- if (NULL == node->parent->args)
- errx(1, "missing display type");
+ assert(node->parent->args);
- for (type = -1, i = 0;
+ for (type = -1, i = 0; -1 == type &&
i < (int)node->parent->args->argc; i++) {
switch (node->parent->args->argv[i].arg) {
case (MDOC_Ragged):
/* FALLTHROUGH */
case (MDOC_Literal):
type = node->parent->args->argv[i].arg;
- i = (int)node->parent->args->argc;
break;
default:
break;
}
}
-
- if (NULL == node->parent->args)
- errx(1, "missing display type");
+
+ assert(type > -1);
i = arg_getattr(MDOC_Offset, node->parent);
- if (-1 != i) {
- if (1 != node->parent->args->argv[i].sz)
- errx(1, "expected single value");
+ if (-1 != i)
p->offset += arg_offset(&node->parent->args->argv[i]);
- }
switch (type) {
case (MDOC_Literal):
return(1);
}
- /*
- * Tricky. Iterate through all children. If we're on a
- * different parse line, append a newline and then the contents.
- * Ew.
- */
-
- p->flags |= TERMP_LITERAL;
- ln = node->child ? node->child->line : 0;
-
for (node = node->child; node; node = node->next) {
- if (ln < node->line) {
- term_flushln(p);
- p->flags |= TERMP_NOSPACE;
- }
- ln = node->line;
+ p->flags |= TERMP_NOSPACE;
print_node(p, pair, meta, node);
+ if (node->next)
+ term_flushln(p);
}
return(0);
if (MDOC_BODY != node->type)
return;
-
- term_flushln(p);
- p->flags &= ~TERMP_LITERAL;
p->flags |= TERMP_NOSPACE;
+ term_flushln(p);
}
}
+/* ARGSUSED */
+static int
+termp_pa_pre(DECL_ARGS)
+{
+
+ pair->flag |= ttypes[TTYPE_FILE];
+ return(1);
+}
+
+
/* ARGSUSED */
static int
termp_pf_pre(DECL_ARGS)
}
-/* ARGSUSED */
-static int
-termp_pa_pre(DECL_ARGS)
-{
-
- pair->flag |= ttypes[TTYPE_FILE];
- return(1);
-}
-
-
/* ARGSUSED */
static int
termp_em_pre(DECL_ARGS)
termp_in_post(DECL_ARGS)
{
- p->flags |= TERMP_NOSPACE;
+ p->flags |= TERMP_NOSPACE | ttypes[TTYPE_INCLUDE];
term_word(p, ">");
+ p->flags &= ~ttypes[TTYPE_INCLUDE];
if (SEC_SYNOPSIS != node->sec)
return;
}
+/* ARGSUSED */
+static int
+termp_sp_pre(DECL_ARGS)
+{
+ int i, len;
+
+ if (NULL == node->child) {
+ term_vspace(p);
+ return(0);
+ }
+
+ len = atoi(node->child->string);
+ if (0 == len)
+ term_newln(p);
+ for (i = 0; i < len; i++)
+ term_vspace(p);
+
+ return(0);
+}
+
+
+/* ARGSUSED */
+static int
+termp_br_pre(DECL_ARGS)
+{
+
+ term_newln(p);
+ return(1);
+}
+
+
/* ARGSUSED */
static int
termp_brq_pre(DECL_ARGS)
termp__t_pre(DECL_ARGS)
{
- term_word(p, "\"");
- p->flags |= TERMP_NOSPACE;
+ pair->flag |= ttypes[TTYPE_REF_TITLE];
return(1);
}
-/* ARGSUSED */
-static void
-termp__t_post(DECL_ARGS)
-{
-
- p->flags |= TERMP_NOSPACE;
- term_word(p, "\"");
- termp____post(p, pair, meta, node);
-}
-
-
/* ARGSUSED */
static void
termp____post(DECL_ARGS)