ref: 690bf91cd6fd3ba6fa04d408a93ca7d108e74f65
parent: e90cb537e4742c28d1da1212a1b22d434a5b7f7b
author: Ali Gholami Rudi <ali@rudi.ir>
date: Thu Oct 16 16:56:19 EDT 2014
fmt: specify the minimum length of paragraph last lines with .pmll The request .pmll sets the minimum length of the last line of formatted paragraphs, specified as a percentage of \n(.l, when formatting paragraphs with .ad p.
--- a/fmt.c
+++ b/fmt.c
@@ -20,6 +20,8 @@
#define FMT_FILL(f) (!n_ce && n_u)
#define FMT_ADJ(f) (n_u && !n_na && !n_ce && (n_j & AD_B) == AD_B)
+static int fmt_fillwords(struct fmt *f, int br);
+
struct word {
char *s;
int wid; /* word's width */
@@ -171,30 +173,57 @@
return l;
}
-static int fmt_sp(struct fmt *f)
+static int fmt_extractline(struct fmt *f, int beg, int end, int llen, int spread)
{
+ int fmt_div, fmt_rem;
+ int w, i, nspc;
struct line *l;
- if (fmt_fill(f))
+ if (!(l = fmt_mkline(f)))
return 1;
- l = fmt_mkline(f);
- if (!l)
+ w = fmt_wordslen(f, beg, end);
+ nspc = fmt_spaces(f, beg, end);
+ /* stretch if (spread & 1) and shrink if (spread & 2) */
+ if (nspc && ((spread & 1 && w < llen) || (spread & 2 && w > llen))) {
+ fmt_div = (llen - w) / nspc;
+ fmt_rem = (llen - w) % nspc;
+ if (fmt_rem < 0) {
+ fmt_div--;
+ fmt_rem += nspc;
+ }
+ for (i = beg + 1; i < end; i++)
+ if (f->words[i].str)
+ f->words[i].gap += fmt_div + (fmt_rem-- > 0);
+ }
+ l->wid = fmt_wordscopy(f, beg, end, &l->sbuf, &l->elsn, &l->elsp);
+ return 0;
+}
+
+static int fmt_sp(struct fmt *f)
+{
+ if (fmt_fillwords(f, 1))
return 1;
+ if (fmt_extractline(f, 0, f->nwords, FMT_LLEN(f) * n_pmll / 100,
+ FMT_ADJ(f) && (n_j & AD_P) == AD_P) ? 1 : 0)
+ return 1;
f->filled = 0;
f->nls--;
f->nls_sup = 0;
- l->wid = fmt_wordscopy(f, 0, f->nwords, &l->sbuf, &l->elsn, &l->elsp);
f->nwords = 0;
f->fillreq = 0;
return 0;
}
-int fmt_br(struct fmt *f)
+/* fill as many lines as possible; if br, put the remaining words in a line */
+int fmt_fill(struct fmt *f, int br)
{
- if (fmt_fill(f))
+ if (fmt_fillwords(f, br))
return 1;
- f->filled = 0;
- if (f->nwords)
- fmt_sp(f);
+ if (br) {
+ f->filled = 0;
+ if (f->nwords)
+ if (fmt_sp(f))
+ return 1;
+ }
return 0;
}
@@ -224,7 +253,7 @@
int fmt_fillreq(struct fmt *f)
{
if (f->fillreq > 0)
- if (fmt_fill(f))
+ if (fmt_fillwords(f, 0))
return 1;
f->fillreq = f->nwords + 1;
return 0;
@@ -314,7 +343,7 @@
if (wb_empty(wb))
return 0;
if (f->nwords + NHYPHSWORD >= NWORDS || fmt_confchanged(f))
- if (fmt_fill(f))
+ if (fmt_fillwords(f, 0))
return 1;
if (FMT_FILL(f) && f->nls && f->gap)
if (fmt_sp(f))
@@ -331,9 +360,13 @@
return 0;
}
-/* assuming an empty line has cost 10000; take care of integer overflow */
-#define POW2(x) ((x) * (x))
-#define FMT_COST(lwid, llen, pen) (POW2(((llen) - (lwid)) * 1000l / (llen)) / 100l + (pen) * 10l)
+/* assuming an empty line has cost 1000; takes care of integer overflow */
+#define POW2(x) ((x) * (x))
+/* the cost of putting lwid words in a line of length llen */
+#define FMT_COST(lwid, llen) (POW2(((llen) - (lwid)) * 1000l / (llen)) / 1000l)
+/* the cost of formatting last lines; should prevent widows */
+#define FMT_LCOST(lwid, llen) (n_pmll && (lwid) < (llen) * n_pmll / 100 ? \
+ FMT_COST((lwid) * 100 / (n_pmll), (llen)) : 0)
/* the cost of putting a line break before word pos */
static long fmt_findcost(struct fmt *f, int pos)
@@ -362,7 +395,7 @@
if (lwid - (swid * n_ssh / 100) > llen)
if (pos - i > 1)
break;
- cur = fmt_findcost(f, i) + FMT_COST(lwid, llen, pen);
+ cur = fmt_findcost(f, i) + FMT_COST(lwid, llen) + pen;
if (f->best_pos[pos] < 0 || cur < f->best[pos]) {
f->best_pos[pos] = i;
f->best_dep[pos] = f->best_dep[i] + 1;
@@ -386,10 +419,11 @@
}
/* return the last filled word */
-static int fmt_breakparagraph(struct fmt *f, int pos)
+static int fmt_breakparagraph(struct fmt *f, int pos, int br)
{
int i;
int best = -1;
+ long cost, best_cost = 0;
int llen = FMT_LLEN(f);
int lwid = 0;
if (f->fillreq > 0 && f->fillreq <= f->nwords) {
@@ -410,8 +444,11 @@
lwid += f->words[i + 1].gap;
if (lwid > llen && i + 1 < pos)
break;
- if (best < 0 || fmt_findcost(f, i) < fmt_findcost(f, best))
+ cost = fmt_findcost(f, i) + (br ? FMT_LCOST(lwid, llen) : 0);
+ if (best < 0 || cost < best_cost) {
best = i;
+ best_cost = cost;
+ }
i--;
}
return best;
@@ -449,32 +486,13 @@
/* break f->words[0..end] into lines according to fmt_bestpos() */
static int fmt_break(struct fmt *f, int end)
{
- int llen, fmt_div, fmt_rem, beg;
- int w, i, nspc;
- struct line *l;
- int ret = 0;
+ int beg, ret = 0;
beg = fmt_bestpos(f, end);
if (beg > 0)
ret += fmt_break(f, beg);
- l = fmt_mkline(f);
- if (!l)
- return ret;
- llen = FMT_LLEN(f);
f->words[beg].gap = 0;
- w = fmt_wordslen(f, beg, end);
- nspc = fmt_spaces(f, beg, end);
- if (FMT_ADJ(f) && nspc) {
- fmt_div = (llen - w) / nspc;
- fmt_rem = (llen - w) % nspc;
- if (fmt_rem < 0) {
- fmt_div--;
- fmt_rem += nspc;
- }
- for (i = beg + 1; i < end; i++)
- if (f->words[i].str)
- f->words[i].gap += fmt_div + (fmt_rem-- > 0);
- }
- l->wid = fmt_wordscopy(f, beg, end, &l->sbuf, &l->elsn, &l->elsp);
+ if (fmt_extractline(f, beg, end, FMT_LLEN(f), FMT_ADJ(f) ? 3 : 0))
+ return ret;
if (beg > 0)
fmt_confupdate(f);
return ret + (end - beg);
@@ -488,7 +506,7 @@
}
/* fill the words collected in the buffer */
-int fmt_fill(struct fmt *f)
+static int fmt_fillwords(struct fmt *f, int br)
{
int nreq; /* the number of lines until a trap */
int end; /* the final line ends before this word */
@@ -509,7 +527,7 @@
/* resetting positions */
for (i = 0; i < f->nwords + 1; i++)
f->best_pos[i] = -1;
- end = fmt_breakparagraph(f, f->nwords);
+ end = fmt_breakparagraph(f, f->nwords, br);
if (nreq > 0) {
end_head = fmt_head(f, nreq - fmt_nlines(f), end);
head = end_head < end;
--- a/reg.c
+++ b/reg.c
@@ -38,7 +38,7 @@
".nS", ".m", ".s", ".u", ".v",
".it", ".itn", ".mc", ".mcn",
".ce", ".f0", ".hy", ".hyp", ".i0", ".l0",
- ".L0", ".m0", ".n0", ".s0", ".ss", ".ssh", ".sss",
+ ".L0", ".m0", ".n0", ".s0", ".ss", ".ssh", ".sss", ".pmll",
".ti", ".lt", ".lt0", ".v0",
};
--- a/ren.c
+++ b/ren.c
@@ -374,16 +374,18 @@
}
/* output formatted lines in fmt */
-static void ren_fmtpop(struct fmt *fmt)
+static int ren_fmtpop(struct fmt *fmt)
{
+ int ret = 0;
while (fmt_morelines(fmt))
- ren_passline(fmt);
+ ret = ren_passline(fmt);
+ return ret;
}
/* format and output all lines in fmt */
static void ren_fmtpopall(struct fmt *fmt)
{
- while (fmt_fill(fmt))
+ while (fmt_fill(fmt, 0))
ren_fmtpop(fmt);
ren_fmtpop(fmt);
}
@@ -401,17 +403,17 @@
{
ren_first();
ren_fmtword(cwb);
- ren_fmtpopall(cfmt);
- while (fmt_br(cfmt))
+ while (fmt_fill(cfmt, 1))
ren_fmtpop(cfmt);
- return ren_passline(cfmt);
+ return ren_fmtpop(cfmt);
}
void tr_br(char **args)
{
- ren_fmtpopall(cfmt); /* output the completed lines first */
if (args[0][0] == c_cc)
ren_br();
+ else
+ ren_fmtpopall(cfmt); /* output the completed lines */
}
void tr_sp(char **args)
--- a/roff.h
+++ b/roff.h
@@ -308,8 +308,7 @@
int fmt_word(struct fmt *fmt, struct wb *wb);
int fmt_newline(struct fmt *fmt);
int fmt_fillreq(struct fmt *f);
-int fmt_br(struct fmt *fmt);
-int fmt_fill(struct fmt *fmt);
+int fmt_fill(struct fmt *fmt, int br);
int fmt_morelines(struct fmt *fmt);
int fmt_morewords(struct fmt *fmt);
int fmt_nextline(struct fmt *fmt, struct sbuf *sbuf, int *w,
@@ -473,6 +472,7 @@
#define n_na (*nreg(map(".na"))) /* .na mode */
#define n_ns (*nreg(map(".ns"))) /* .ns mode */
#define n_o0 (*nreg(map(".o0"))) /* last .o */
+#define n_pmll (*nreg(map(".pmll"))) /* minimum last line (.pmll) */
#define n_ss (*nreg(map(".ss"))) /* word space (.ss) */
#define n_sss (*nreg(map(".sss"))) /* sentence space (.ss) */
#define n_ssh (*nreg(map(".ssh"))) /* word space compression (.ssh) */
--- a/tr.c
+++ b/tr.c
@@ -424,6 +424,11 @@
n_hyp = args[1] ? atoi(args[1]) : 1;
}
+static void tr_pmll(char **args)
+{
+ n_pmll = args[1] ? atoi(args[1]) : 0;
+}
+
static void tr_lg(char **args)
{
if (args[1])
@@ -942,6 +947,7 @@
{"os", tr_os},
{"pc", tr_pc},
{"pl", tr_pl},
+ {"pmll", tr_pmll},
{"pn", tr_pn},
{"po", tr_po},
{"ps", tr_ps},