shithub: neatroff

Download patch

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},