![]() ![]() | ![]() |
version 1.36, 2011/01/25 12:07:30 | version 1.64, 2017/06/08 12:54:58 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> | * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> | ||
* Copyright (c) 2011, 2014, 2015, 2017 Ingo Schwarze <schwarze@openbsd.org> | |||
* | * | ||
* Permission to use, copy, modify, and distribute this software for any | * Permission to use, copy, modify, and distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | * purpose with or without fee is hereby granted, provided that the above | ||
|
|
||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*/ | */ | ||
#ifdef HAVE_CONFIG_H | |||
#include "config.h" | #include "config.h" | ||
#endif | |||
#include <sys/types.h> | #include <sys/types.h> | ||
#include <assert.h> | #include <assert.h> | ||
#include <ctype.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | #include <stdlib.h> | ||
#include <string.h> | #include <string.h> | ||
#include <time.h> | #include <time.h> | ||
#include "mandoc_aux.h" | |||
#include "mandoc.h" | #include "mandoc.h" | ||
#include "out.h" | #include "out.h" | ||
static void tblcalc_data(struct rofftbl *, struct roffcol *, | static void tblcalc_data(struct rofftbl *, struct roffcol *, | ||
const struct tbl *, const struct tbl_dat *); | const struct tbl_opts *, const struct tbl_dat *); | ||
static void tblcalc_literal(struct rofftbl *, struct roffcol *, | static void tblcalc_literal(struct rofftbl *, struct roffcol *, | ||
const struct tbl_dat *); | const struct tbl_dat *); | ||
static void tblcalc_number(struct rofftbl *, struct roffcol *, | static void tblcalc_number(struct rofftbl *, struct roffcol *, | ||
const struct tbl *, const struct tbl_dat *); | const struct tbl_opts *, const struct tbl_dat *); | ||
/* | |||
* Convert a `scaling unit' to a consistent form, or fail. Scaling | /* | ||
* units are documented in groff.7, mdoc.7, man.7. | * Parse the *src string and store a scaling unit into *dst. | ||
* If the string doesn't specify the unit, use the default. | |||
* If no default is specified, fail. | |||
* Return a pointer to the byte after the last byte used, | |||
* or NULL on total failure. | |||
*/ | */ | ||
int | const char * | ||
a2roffsu(const char *src, struct roffsu *dst, enum roffscale def) | a2roffsu(const char *src, struct roffsu *dst, enum roffscale def) | ||
{ | { | ||
char buf[BUFSIZ], hasd; | char *endptr; | ||
int i; | |||
enum roffscale unit; | |||
if ('\0' == *src) | dst->unit = def == SCALE_MAX ? SCALE_BU : def; | ||
return(0); | dst->scale = strtod(src, &endptr); | ||
if (endptr == src) | |||
return NULL; | |||
i = hasd = 0; | switch (*endptr++) { | ||
case 'c': | |||
switch (*src) { | dst->unit = SCALE_CM; | ||
case ('+'): | |||
src++; | |||
break; | break; | ||
case ('-'): | case 'i': | ||
buf[i++] = *src++; | dst->unit = SCALE_IN; | ||
break; | break; | ||
default: | case 'f': | ||
dst->unit = SCALE_FS; | |||
break; | break; | ||
} | case 'M': | ||
dst->unit = SCALE_MM; | |||
if ('\0' == *src) | |||
return(0); | |||
while (i < BUFSIZ) { | |||
if ( ! isdigit((u_char)*src)) { | |||
if ('.' != *src) | |||
break; | |||
else if (hasd) | |||
break; | |||
else | |||
hasd = 1; | |||
} | |||
buf[i++] = *src++; | |||
} | |||
if (BUFSIZ == i || (*src && *(src + 1))) | |||
return(0); | |||
buf[i] = '\0'; | |||
switch (*src) { | |||
case ('c'): | |||
unit = SCALE_CM; | |||
break; | break; | ||
case ('i'): | case 'm': | ||
unit = SCALE_IN; | dst->unit = SCALE_EM; | ||
break; | break; | ||
case ('P'): | case 'n': | ||
unit = SCALE_PC; | dst->unit = SCALE_EN; | ||
break; | break; | ||
case ('p'): | case 'P': | ||
unit = SCALE_PT; | dst->unit = SCALE_PC; | ||
break; | break; | ||
case ('f'): | case 'p': | ||
unit = SCALE_FS; | dst->unit = SCALE_PT; | ||
break; | break; | ||
case ('v'): | case 'u': | ||
unit = SCALE_VS; | dst->unit = SCALE_BU; | ||
break; | break; | ||
case ('m'): | case 'v': | ||
unit = SCALE_EM; | dst->unit = SCALE_VS; | ||
break; | break; | ||
case ('\0'): | case '\0': | ||
if (SCALE_MAX == def) | endptr--; | ||
return(0); | |||
unit = SCALE_BU; | |||
break; | |||
case ('u'): | |||
unit = SCALE_BU; | |||
break; | |||
case ('M'): | |||
unit = SCALE_MM; | |||
break; | |||
case ('n'): | |||
unit = SCALE_EN; | |||
break; | |||
default: | |||
return(0); | |||
} | |||
/* FIXME: do this in the caller. */ | |||
if ((dst->scale = atof(buf)) < 0) | |||
dst->scale = 0; | |||
dst->unit = unit; | |||
return(1); | |||
} | |||
/* | |||
* Correctly writes the time in nroff form, which differs from standard | |||
* form in that a space isn't printed in lieu of the extra %e field for | |||
* single-digit dates. | |||
*/ | |||
void | |||
time2a(time_t t, char *dst, size_t sz) | |||
{ | |||
struct tm tm; | |||
char buf[5]; | |||
char *p; | |||
size_t nsz; | |||
assert(sz > 1); | |||
localtime_r(&t, &tm); | |||
p = dst; | |||
nsz = 0; | |||
dst[0] = '\0'; | |||
if (0 == (nsz = strftime(p, sz, "%B ", &tm))) | |||
return; | |||
p += (int)nsz; | |||
sz -= nsz; | |||
if (0 == strftime(buf, sizeof(buf), "%e, ", &tm)) | |||
return; | |||
nsz = strlcat(p, buf + (' ' == buf[0] ? 1 : 0), sz); | |||
if (nsz >= sz) | |||
return; | |||
p += (int)nsz; | |||
sz -= nsz; | |||
(void)strftime(p, sz, "%Y", &tm); | |||
} | |||
int | |||
a2roffdeco(enum roffdeco *d, const char **word, size_t *sz) | |||
{ | |||
int i, j, lim; | |||
char term, c; | |||
const char *wp; | |||
enum roffdeco dd; | |||
*d = DECO_NONE; | |||
lim = i = 0; | |||
term = '\0'; | |||
wp = *word; | |||
switch ((c = wp[i++])) { | |||
case ('('): | |||
*d = DECO_SPECIAL; | |||
lim = 2; | |||
break; | |||
case ('F'): | |||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||
case ('f'): | |||
*d = 'F' == c ? DECO_FFONT : DECO_FONT; | |||
switch (wp[i++]) { | |||
case ('('): | |||
lim = 2; | |||
break; | |||
case ('['): | |||
term = ']'; | |||
break; | |||
case ('3'): | |||
/* FALLTHROUGH */ | |||
case ('B'): | |||
*d = DECO_BOLD; | |||
return(i); | |||
case ('2'): | |||
/* FALLTHROUGH */ | |||
case ('I'): | |||
*d = DECO_ITALIC; | |||
return(i); | |||
case ('P'): | |||
*d = DECO_PREVIOUS; | |||
return(i); | |||
case ('1'): | |||
/* FALLTHROUGH */ | |||
case ('R'): | |||
*d = DECO_ROMAN; | |||
return(i); | |||
default: | |||
i--; | |||
lim = 1; | |||
break; | |||
} | |||
break; | |||
case ('k'): | |||
/* FALLTHROUGH */ | |||
case ('M'): | |||
/* FALLTHROUGH */ | |||
case ('m'): | |||
/* FALLTHROUGH */ | |||
case ('*'): | |||
if ('*' == c) | |||
*d = DECO_RESERVED; | |||
switch (wp[i++]) { | |||
case ('('): | |||
lim = 2; | |||
break; | |||
case ('['): | |||
term = ']'; | |||
break; | |||
default: | |||
i--; | |||
lim = 1; | |||
break; | |||
} | |||
break; | |||
case ('h'): | |||
/* FALLTHROUGH */ | |||
case ('v'): | |||
/* FALLTHROUGH */ | |||
case ('s'): | |||
j = 0; | |||
if ('+' == wp[i] || '-' == wp[i]) { | |||
i++; | |||
j = 1; | |||
} | |||
switch (wp[i++]) { | |||
case ('('): | |||
lim = 2; | |||
break; | |||
case ('['): | |||
term = ']'; | |||
break; | |||
case ('\''): | |||
term = '\''; | |||
break; | |||
case ('0'): | |||
j = 1; | |||
/* FALLTHROUGH */ | |||
default: | |||
i--; | |||
lim = 1; | |||
break; | |||
} | |||
if ('+' == wp[i] || '-' == wp[i]) { | |||
if (j) | |||
return(i); | |||
i++; | |||
} | |||
/* Handle embedded numerical subexp or escape. */ | |||
if ('(' == wp[i]) { | |||
while (wp[i] && ')' != wp[i]) | |||
if ('\\' == wp[i++]) { | |||
/* Handle embedded escape. */ | |||
*word = &wp[i]; | |||
i += a2roffdeco(&dd, word, sz); | |||
} | |||
if (')' == wp[i++]) | |||
break; | |||
*d = DECO_NONE; | |||
return(i - 1); | |||
} else if ('\\' == wp[i]) { | |||
*word = &wp[++i]; | |||
i += a2roffdeco(&dd, word, sz); | |||
} | |||
break; | |||
case ('['): | |||
*d = DECO_SPECIAL; | |||
term = ']'; | |||
break; | |||
case ('c'): | |||
*d = DECO_NOSPACE; | |||
return(i); | |||
case ('z'): | |||
*d = DECO_NONE; | |||
if ('\\' == wp[i]) { | |||
*word = &wp[++i]; | |||
return(i + a2roffdeco(&dd, word, sz)); | |||
} else | |||
lim = 1; | |||
break; | |||
case ('o'): | |||
/* FALLTHROUGH */ | |||
case ('w'): | |||
if ('\'' == wp[i++]) { | |||
term = '\''; | |||
break; | |||
} | |||
/* FALLTHROUGH */ | |||
default: | default: | ||
*d = DECO_SSPECIAL; | if (SCALE_MAX == def) | ||
i--; | return NULL; | ||
lim = 1; | dst->unit = def; | ||
break; | break; | ||
} | } | ||
return endptr; | |||
assert(term || lim); | |||
*word = &wp[i]; | |||
if (term) { | |||
j = i; | |||
while (wp[i] && wp[i] != term) | |||
i++; | |||
if ('\0' == wp[i]) { | |||
*d = DECO_NONE; | |||
return(i); | |||
} | |||
assert(i >= j); | |||
*sz = (size_t)(i - j); | |||
return(i + 1); | |||
} | |||
assert(lim > 0); | |||
*sz = (size_t)lim; | |||
for (j = 0; wp[i] && j < lim; j++) | |||
i++; | |||
if (j < lim) | |||
*d = DECO_NONE; | |||
return(i); | |||
} | } | ||
/* | /* | ||
|
|
||
* used for the actual width calculations. | * used for the actual width calculations. | ||
*/ | */ | ||
void | void | ||
tblcalc(struct rofftbl *tbl, const struct tbl_span *sp) | tblcalc(struct rofftbl *tbl, const struct tbl_span *sp, | ||
size_t totalwidth) | |||
{ | { | ||
const struct tbl_opts *opts; | |||
const struct tbl_dat *dp; | const struct tbl_dat *dp; | ||
const struct tbl_head *hp; | |||
struct roffcol *col; | struct roffcol *col; | ||
size_t ewidth, xwidth; | |||
int spans; | |||
int icol, maxcol, necol, nxcol, quirkcol; | |||
/* | /* | ||
* Allocate the master column specifiers. These will hold the | * Allocate the master column specifiers. These will hold the | ||
|
|
||
*/ | */ | ||
assert(NULL == tbl->cols); | assert(NULL == tbl->cols); | ||
tbl->cols = calloc(sp->tbl->cols, sizeof(struct roffcol)); | tbl->cols = mandoc_calloc((size_t)sp->opts->cols, | ||
sizeof(struct roffcol)); | |||
opts = sp->opts; | |||
hp = sp->head; | for (maxcol = -1; sp; sp = sp->next) { | ||
for ( ; sp; sp = sp->next) { | |||
if (TBL_SPAN_DATA != sp->pos) | if (TBL_SPAN_DATA != sp->pos) | ||
continue; | continue; | ||
spans = 1; | |||
/* | /* | ||
* Account for the data cells in the layout, matching it | * Account for the data cells in the layout, matching it | ||
* to data cells in the data section. | * to data cells in the data section. | ||
*/ | */ | ||
for (dp = sp->first; dp; dp = dp->next) { | for (dp = sp->first; dp; dp = dp->next) { | ||
assert(dp->layout); | /* Do not used spanned cells in the calculation. */ | ||
col = &tbl->cols[dp->layout->head->ident]; | if (0 < --spans) | ||
tblcalc_data(tbl, col, sp->tbl, dp); | continue; | ||
spans = dp->spans; | |||
if (1 < spans) | |||
continue; | |||
icol = dp->layout->col; | |||
if (maxcol < icol) | |||
maxcol = icol; | |||
col = tbl->cols + icol; | |||
col->flags |= dp->layout->flags; | |||
if (dp->layout->flags & TBL_CELL_WIGN) | |||
continue; | |||
tblcalc_data(tbl, col, opts, dp); | |||
} | } | ||
} | } | ||
/* | /* | ||
* Calculate width of the spanners. These get one space for a | * Count columns to equalize and columns to maximize. | ||
* vertical line, two for a double-vertical line. | * Find maximum width of the columns to equalize. | ||
* Find total width of the columns *not* to maximize. | |||
*/ | */ | ||
for ( ; hp; hp = hp->next) { | necol = nxcol = 0; | ||
col = &tbl->cols[hp->ident]; | ewidth = xwidth = 0; | ||
switch (hp->pos) { | for (icol = 0; icol <= maxcol; icol++) { | ||
case (TBL_HEAD_VERT): | col = tbl->cols + icol; | ||
col->width = (*tbl->len)(1, tbl->arg); | if (col->flags & TBL_CELL_EQUAL) { | ||
break; | necol++; | ||
case (TBL_HEAD_DVERT): | if (ewidth < col->width) | ||
col->width = (*tbl->len)(2, tbl->arg); | ewidth = col->width; | ||
break; | |||
default: | |||
break; | |||
} | } | ||
if (col->flags & TBL_CELL_WMAX) | |||
nxcol++; | |||
else | |||
xwidth += col->width; | |||
} | } | ||
/* | |||
* Equalize columns, if requested for any of them. | |||
* Update total width of the columns not to maximize. | |||
*/ | |||
if (necol) { | |||
for (icol = 0; icol <= maxcol; icol++) { | |||
col = tbl->cols + icol; | |||
if ( ! (col->flags & TBL_CELL_EQUAL)) | |||
continue; | |||
if (col->width == ewidth) | |||
continue; | |||
if (nxcol && totalwidth) | |||
xwidth += ewidth - col->width; | |||
col->width = ewidth; | |||
} | |||
} | |||
/* | |||
* If there are any columns to maximize, find the total | |||
* available width, deducting 3n margins between columns. | |||
* Distribute the available width evenly. | |||
*/ | |||
if (nxcol && totalwidth) { | |||
xwidth += 3*maxcol + | |||
(opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ? | |||
2 : !!opts->lvert + !!opts->rvert); | |||
if (xwidth >= totalwidth) | |||
return; | |||
xwidth = totalwidth - xwidth; | |||
/* | |||
* Emulate a bug in GNU tbl width calculation that | |||
* manifests itself for large numbers of x-columns. | |||
* Emulating it for 5 x-columns gives identical | |||
* behaviour for up to 6 x-columns. | |||
*/ | |||
if (nxcol == 5) { | |||
quirkcol = xwidth % nxcol + 2; | |||
if (quirkcol != 3 && quirkcol != 4) | |||
quirkcol = -1; | |||
} else | |||
quirkcol = -1; | |||
necol = 0; | |||
ewidth = 0; | |||
for (icol = 0; icol <= maxcol; icol++) { | |||
col = tbl->cols + icol; | |||
if ( ! (col->flags & TBL_CELL_WMAX)) | |||
continue; | |||
col->width = (double)xwidth * ++necol / nxcol | |||
- ewidth + 0.4995; | |||
if (necol == quirkcol) | |||
col->width--; | |||
ewidth += col->width; | |||
} | |||
} | |||
} | } | ||
static void | static void | ||
tblcalc_data(struct rofftbl *tbl, struct roffcol *col, | tblcalc_data(struct rofftbl *tbl, struct roffcol *col, | ||
const struct tbl *tp, const struct tbl_dat *dp) | const struct tbl_opts *opts, const struct tbl_dat *dp) | ||
{ | { | ||
size_t sz; | size_t sz; | ||
/* Branch down into data sub-types. */ | /* Branch down into data sub-types. */ | ||
switch (dp->layout->pos) { | switch (dp->layout->pos) { | ||
case (TBL_CELL_HORIZ): | case TBL_CELL_HORIZ: | ||
/* FALLTHROUGH */ | case TBL_CELL_DHORIZ: | ||
case (TBL_CELL_DHORIZ): | |||
sz = (*tbl->len)(1, tbl->arg); | sz = (*tbl->len)(1, tbl->arg); | ||
if (col->width < sz) | if (col->width < sz) | ||
col->width = sz; | col->width = sz; | ||
break; | break; | ||
case (TBL_CELL_LONG): | case TBL_CELL_LONG: | ||
/* FALLTHROUGH */ | case TBL_CELL_CENTRE: | ||
case (TBL_CELL_CENTRE): | case TBL_CELL_LEFT: | ||
/* FALLTHROUGH */ | case TBL_CELL_RIGHT: | ||
case (TBL_CELL_LEFT): | |||
/* FALLTHROUGH */ | |||
case (TBL_CELL_RIGHT): | |||
tblcalc_literal(tbl, col, dp); | tblcalc_literal(tbl, col, dp); | ||
break; | break; | ||
case (TBL_CELL_NUMBER): | case TBL_CELL_NUMBER: | ||
tblcalc_number(tbl, col, tp, dp); | tblcalc_number(tbl, col, opts, dp); | ||
break; | break; | ||
case (TBL_CELL_DOWN): | case TBL_CELL_DOWN: | ||
break; | break; | ||
default: | default: | ||
abort(); | abort(); | ||
/* NOTREACHED */ | |||
} | } | ||
} | } | ||
|
|
||
tblcalc_literal(struct rofftbl *tbl, struct roffcol *col, | tblcalc_literal(struct rofftbl *tbl, struct roffcol *col, | ||
const struct tbl_dat *dp) | const struct tbl_dat *dp) | ||
{ | { | ||
size_t sz, bufsz, spsz; | size_t sz; | ||
const char *str; | const char *str; | ||
/* | |||
* Calculate our width and use the spacing, with a minimum | |||
* spacing dictated by position (centre, e.g,. gets a space on | |||
* either side, while right/left get a single adjacent space). | |||
*/ | |||
bufsz = spsz = 0; | |||
str = dp->string ? dp->string : ""; | str = dp->string ? dp->string : ""; | ||
sz = (*tbl->slen)(str, tbl->arg); | sz = (*tbl->slen)(str, tbl->arg); | ||
/* FIXME: TBL_DATA_HORIZ et al.? */ | |||
assert(dp->layout); | |||
switch (dp->layout->pos) { | |||
case (TBL_CELL_LONG): | |||
/* FALLTHROUGH */ | |||
case (TBL_CELL_CENTRE): | |||
bufsz = (*tbl->len)(1, tbl->arg); | |||
break; | |||
default: | |||
bufsz = (*tbl->len)(1, tbl->arg); | |||
break; | |||
} | |||
if (dp->layout->spacing) { | |||
spsz = (*tbl->len)(dp->layout->spacing, tbl->arg); | |||
bufsz = bufsz > spsz ? bufsz : spsz; | |||
} | |||
sz += bufsz; | |||
if (col->width < sz) | if (col->width < sz) | ||
col->width = sz; | col->width = sz; | ||
} | } | ||
static void | static void | ||
tblcalc_number(struct rofftbl *tbl, struct roffcol *col, | tblcalc_number(struct rofftbl *tbl, struct roffcol *col, | ||
const struct tbl *tp, const struct tbl_dat *dp) | const struct tbl_opts *opts, const struct tbl_dat *dp) | ||
{ | { | ||
int i; | int i; | ||
size_t sz, psz, ssz, d; | size_t sz, psz, ssz, d; | ||
const char *str; | const char *str; | ||
char *cp; | char *cp; | ||
|
|
||
/* | /* | ||
* First calculate number width and decimal place (last + 1 for | * First calculate number width and decimal place (last + 1 for | ||
* no-decimal numbers). If the stored decimal is subsequent | * non-decimal numbers). If the stored decimal is subsequent to | ||
* ours, make our size longer by that difference | * ours, make our size longer by that difference | ||
* (right-"shifting"); similarly, if ours is subsequent the | * (right-"shifting"); similarly, if ours is subsequent the | ||
* stored, then extend the stored size by the difference. | * stored, then extend the stored size by the difference. | ||
|
|
||
/* FIXME: TBL_DATA_HORIZ et al.? */ | /* FIXME: TBL_DATA_HORIZ et al.? */ | ||
buf[0] = tp->decimal; | buf[0] = opts->decimal; | ||
buf[1] = '\0'; | buf[1] = '\0'; | ||
psz = (*tbl->slen)(buf, tbl->arg); | psz = (*tbl->slen)(buf, tbl->arg); | ||
if (NULL != (cp = strrchr(str, tp->decimal))) { | if (NULL != (cp = strrchr(str, opts->decimal))) { | ||
buf[1] = '\0'; | buf[1] = '\0'; | ||
for (ssz = 0, i = 0; cp != &str[i]; i++) { | for (ssz = 0, i = 0; cp != &str[i]; i++) { | ||
buf[0] = str[i]; | buf[0] = str[i]; | ||
|
|
||
} else | } else | ||
d = sz + psz; | d = sz + psz; | ||
/* Padding. */ | |||
sz += (*tbl->len)(2, tbl->arg); | |||
d += (*tbl->len)(1, tbl->arg); | |||
/* Adjust the settings for this column. */ | /* Adjust the settings for this column. */ | ||
if (col->decimal > d) { | if (col->decimal > d) { | ||
|
|
||
col->width = sz; | col->width = sz; | ||
if (d > col->decimal) | if (d > col->decimal) | ||
col->decimal = d; | col->decimal = d; | ||
/* Adjust for stipulated width. */ | |||
if (col->width < dp->layout->spacing) | |||
col->width = dp->layout->spacing; | |||
} | } | ||