summaryrefslogtreecommitdiffstats
path: root/out.c
diff options
context:
space:
mode:
authorIngo Schwarze <schwarze@openbsd.org>2018-11-29 01:55:02 +0000
committerIngo Schwarze <schwarze@openbsd.org>2018-11-29 01:55:02 +0000
commitdc00978363cd47c103609223ef1090b139429f0d (patch)
treec413b4541326dd40bf9de62fffe19d9a317a7df7 /out.c
parentdb1390e39e0751b57a677bb7edf1f467c164f584 (diff)
downloadmandoc-dc00978363cd47c103609223ef1090b139429f0d.tar.gz
Better handle automatic column width assignments in the presence of
horizontal spans, by implementing a moderately difficult iterative algoritm. The benefit is that spans containing long text no longer cause an excessive width of their starting column. The result is likely not optimal, in particular in the presence of many spans overlapping in complicated ways nor when spans interact with equalizing or maximizing colums. But i doubt the practical usefulness of making this more complicated. Issue originally reported in synaptics(4), which now looks better, by tedu@ three years ago, and reminded by Pali Rohar this summer.
Diffstat (limited to 'out.c')
-rw-r--r--out.c247
1 files changed, 205 insertions, 42 deletions
diff --git a/out.c b/out.c
index 43a4ca4b..3dc9400d 100644
--- a/out.c
+++ b/out.c
@@ -30,12 +30,19 @@
#include "mandoc.h"
#include "out.h"
-static void tblcalc_data(struct rofftbl *, struct roffcol *,
+struct tbl_colgroup {
+ struct tbl_colgroup *next;
+ size_t wanted;
+ int startcol;
+ int endcol;
+};
+
+static size_t tblcalc_data(struct rofftbl *, struct roffcol *,
const struct tbl_opts *, const struct tbl_dat *,
size_t);
-static void tblcalc_literal(struct rofftbl *, struct roffcol *,
+static size_t tblcalc_literal(struct rofftbl *, struct roffcol *,
const struct tbl_dat *, size_t);
-static void tblcalc_number(struct rofftbl *, struct roffcol *,
+static size_t tblcalc_number(struct rofftbl *, struct roffcol *,
const struct tbl_opts *, const struct tbl_dat *);
@@ -104,16 +111,18 @@ a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
* used for the actual width calculations.
*/
void
-tblcalc(struct rofftbl *tbl, const struct tbl_span *sp,
+tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first,
size_t offset, size_t rmargin)
{
struct roffsu su;
const struct tbl_opts *opts;
+ const struct tbl_span *sp;
const struct tbl_dat *dp;
struct roffcol *col;
- size_t ewidth, xwidth;
- int hspans;
- int icol, maxcol, necol, nxcol, quirkcol;
+ struct tbl_colgroup *first_group, **gp, *g;
+ size_t *colwidth;
+ size_t ewidth, min1, min2, wanted, width, xwidth;
+ int done, icol, maxcol, necol, nxcol, quirkcol;
/*
* Allocate the master column specifiers. These will hold the
@@ -121,33 +130,34 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp,
* must be freed and nullified by the caller.
*/
- assert(NULL == tbl->cols);
- tbl->cols = mandoc_calloc((size_t)sp->opts->cols,
+ assert(tbl->cols == NULL);
+ tbl->cols = mandoc_calloc((size_t)sp_first->opts->cols,
sizeof(struct roffcol));
- opts = sp->opts;
+ opts = sp_first->opts;
- for (maxcol = -1; sp; sp = sp->next) {
- if (TBL_SPAN_DATA != sp->pos)
+ maxcol = -1;
+ first_group = NULL;
+ for (sp = sp_first; sp != NULL; sp = sp->next) {
+ if (sp->pos != TBL_SPAN_DATA)
continue;
- hspans = 1;
+
/*
* Account for the data cells in the layout, matching it
* to data cells in the data section.
*/
- for (dp = sp->first; dp; dp = dp->next) {
- /* Do not used spanned cells in the calculation. */
- if (0 < --hspans)
- continue;
- hspans = dp->hspans;
- if (1 < hspans)
- continue;
+
+ gp = &first_group;
+ for (dp = sp->first; dp != NULL; dp = dp->next) {
icol = dp->layout->col;
- while (maxcol < icol)
+ while (icol > maxcol)
tbl->cols[++maxcol].spacing = SIZE_MAX;
col = tbl->cols + icol;
col->flags |= dp->layout->flags;
if (dp->layout->flags & TBL_CELL_WIGN)
continue;
+
+ /* Handle explicit width specifications. */
+
if (dp->layout->wstr != NULL &&
dp->layout->width == 0 &&
a2roffsu(dp->layout->wstr, &su, SCALE_EN)
@@ -160,13 +170,162 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp,
(col->spacing == SIZE_MAX ||
col->spacing < dp->layout->spacing))
col->spacing = dp->layout->spacing;
- tblcalc_data(tbl, col, opts, dp,
+
+ /*
+ * Calculate an automatic width.
+ * Except for spanning cells, apply it.
+ */
+
+ width = tblcalc_data(tbl,
+ dp->hspans == 0 ? col : NULL,
+ opts, dp,
dp->block == 0 ? 0 :
dp->layout->width ? dp->layout->width :
rmargin ? (rmargin + sp->opts->cols / 2)
/ (sp->opts->cols + 1) : 0);
+ if (dp->hspans == 0)
+ continue;
+
+ /*
+ * Build an ordered, singly linked list
+ * of all groups of columns joined by spans,
+ * recording the minimum width for each group.
+ */
+
+ while (*gp != NULL && ((*gp)->startcol < icol ||
+ (*gp)->endcol < icol + dp->hspans))
+ gp = &(*gp)->next;
+ if (*gp == NULL || (*gp)->startcol > icol ||
+ (*gp)->endcol > icol + dp->hspans) {
+ g = mandoc_malloc(sizeof(*g));
+ g->next = *gp;
+ g->wanted = width;
+ g->startcol = icol;
+ g->endcol = icol + dp->hspans;
+ *gp = g;
+ } else if ((*gp)->wanted < width)
+ (*gp)->wanted = width;
+ }
+ }
+
+ /*
+ * Column spacings are needed for span width calculations,
+ * so set the default values now.
+ */
+
+ for (icol = 0; icol <= maxcol; icol++)
+ if (tbl->cols[icol].spacing == SIZE_MAX || icol == maxcol)
+ tbl->cols[icol].spacing = 3;
+
+ /*
+ * Replace the minimum widths with the missing widths,
+ * and dismiss groups that are already wide enough.
+ */
+
+ gp = &first_group;
+ while ((g = *gp) != NULL) {
+ done = 0;
+ for (icol = g->startcol; icol <= g->endcol; icol++) {
+ width = tbl->cols[icol].width;
+ if (icol < g->endcol)
+ width += tbl->cols[icol].spacing;
+ if (g->wanted <= width) {
+ done = 1;
+ break;
+ } else
+ (*gp)->wanted -= width;
+ }
+ if (done) {
+ *gp = g->next;
+ free(g);
+ } else
+ gp = &(*gp)->next;
+ }
+
+ colwidth = mandoc_reallocarray(NULL, maxcol + 1, sizeof(*colwidth));
+ while (first_group != NULL) {
+
+ /*
+ * Rebuild the array of the widths of all columns
+ * participating in spans that require expansion.
+ */
+
+ for (icol = 0; icol <= maxcol; icol++)
+ colwidth[icol] = SIZE_MAX;
+ for (g = first_group; g != NULL; g = g->next)
+ for (icol = g->startcol; icol <= g->endcol; icol++)
+ colwidth[icol] = tbl->cols[icol].width;
+
+ /*
+ * Find the smallest and second smallest column width
+ * among the columns which may need expamsion.
+ */
+
+ min1 = min2 = SIZE_MAX;
+ for (icol = 0; icol <= maxcol; icol++) {
+ if (min1 > colwidth[icol]) {
+ min2 = min1;
+ min1 = colwidth[icol];
+ } else if (min1 < colwidth[icol] &&
+ min2 > colwidth[icol])
+ min2 = colwidth[icol];
}
+
+ /*
+ * Find the minimum wanted width
+ * for any one of the narrowest columns,
+ * and mark the columns wanting that width.
+ */
+
+ wanted = min2;
+ for (g = first_group; g != NULL; g = g->next) {
+ necol = 0;
+ for (icol = g->startcol; icol <= g->endcol; icol++)
+ if (tbl->cols[icol].width == min1)
+ necol++;
+ if (necol == 0)
+ continue;
+ width = min1 + (g->wanted - 1) / necol + 1;
+ if (width > min2)
+ width = min2;
+ if (wanted > width)
+ wanted = width;
+ for (icol = g->startcol; icol <= g->endcol; icol++)
+ if (colwidth[icol] == min1 ||
+ (colwidth[icol] < min2 &&
+ colwidth[icol] > width))
+ colwidth[icol] = width;
+ }
+
+ /* Record the effect of the widening on the group list. */
+
+ gp = &first_group;
+ while ((g = *gp) != NULL) {
+ done = 0;
+ for (icol = g->startcol; icol <= g->endcol; icol++) {
+ if (colwidth[icol] != wanted ||
+ tbl->cols[icol].width == wanted)
+ continue;
+ if (g->wanted <= wanted - min1) {
+ done = 1;
+ break;
+ }
+ g->wanted -= wanted - min1;
+ }
+ if (done) {
+ *gp = g->next;
+ free(g);
+ } else
+ gp = &(*gp)->next;
+ }
+
+ /* Record the effect of the widening on the columns. */
+
+ for (icol = 0; icol <= maxcol; icol++)
+ if (colwidth[icol] == wanted)
+ tbl->cols[icol].width = wanted;
}
+ free(colwidth);
/*
* Align numbers with text.
@@ -183,8 +342,6 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp,
col->decimal += (col->width - col->nwidth) / 2;
else
col->width = col->nwidth;
- if (col->spacing == SIZE_MAX || icol == maxcol)
- col->spacing = 3;
if (col->flags & TBL_CELL_EQUAL) {
necol++;
if (ewidth < col->width)
@@ -257,7 +414,7 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp,
}
}
-static void
+static size_t
tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
const struct tbl_opts *opts, const struct tbl_dat *dp, size_t mw)
{
@@ -269,26 +426,24 @@ tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
case TBL_CELL_HORIZ:
case TBL_CELL_DHORIZ:
sz = (*tbl->len)(1, tbl->arg);
- if (col->width < sz)
+ if (col != NULL && col->width < sz)
col->width = sz;
- break;
+ return sz;
case TBL_CELL_LONG:
case TBL_CELL_CENTRE:
case TBL_CELL_LEFT:
case TBL_CELL_RIGHT:
- tblcalc_literal(tbl, col, dp, mw);
- break;
+ return tblcalc_literal(tbl, col, dp, mw);
case TBL_CELL_NUMBER:
- tblcalc_number(tbl, col, opts, dp);
- break;
+ return tblcalc_number(tbl, col, opts, dp);
case TBL_CELL_DOWN:
- break;
+ return 0;
default:
abort();
}
}
-static void
+static size_t
tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
const struct tbl_dat *dp, size_t mw)
{
@@ -297,11 +452,12 @@ tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
char *end; /* End of the current line. */
size_t lsz; /* Length of the current line. */
size_t wsz; /* Length of the current word. */
+ size_t msz; /* Length of the longest line. */
if (dp->string == NULL || *dp->string == '\0')
- return;
+ return 0;
str = mw ? mandoc_strdup(dp->string) : dp->string;
- lsz = 0;
+ msz = lsz = 0;
for (beg = str; beg != NULL && *beg != '\0'; beg = end) {
end = mw ? strchr(beg, ' ') : NULL;
if (end != NULL) {
@@ -314,14 +470,17 @@ tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
lsz += 1 + wsz;
else
lsz = wsz;
- if (col->width < lsz)
- col->width = lsz;
+ if (msz < lsz)
+ msz = lsz;
}
if (mw)
free((void *)str);
+ if (col != NULL && col->width < msz)
+ col->width = msz;
+ return msz;
}
-static void
+static size_t
tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
const struct tbl_opts *opts, const struct tbl_dat *dp)
{
@@ -330,7 +489,11 @@ tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
char buf[2];
if (dp->string == NULL || *dp->string == '\0')
- return;
+ return 0;
+
+ totsz = (*tbl->slen)(dp->string, tbl->arg);
+ if (col == NULL)
+ return totsz;
/*
* Find the last digit and
@@ -353,11 +516,10 @@ tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
/* Not a number, treat as a literal string. */
- totsz = (*tbl->slen)(dp->string, tbl->arg);
if (lastdigit == NULL) {
- if (col->width < totsz)
+ if (col != NULL && col->width < totsz)
col->width = totsz;
- return;
+ return totsz;
}
/* Measure the width of the integer part. */
@@ -387,4 +549,5 @@ tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
if (totsz > col->nwidth)
col->nwidth = totsz;
+ return totsz;
}