shithub: neatroff

Download patch

ref: e69b19f1a9b2abb086d2c3e0483a77268689c638
parent: 845970d2c992db3356cc2eae0b4e678b44a577af
author: Ali Gholami Rudi <ali@rudi.ir>
date: Wed May 1 15:01:23 EDT 2013

wb: add wb struct as a word buffer

Now words are collected in wb structs and then added to the
adjustment buffer.  ren_char() now requires a wb struct and
can be called recursively.

--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@
 all: xroff
 %.o: %.c xroff.h
 	$(CC) -c $(CFLAGS) $<
-xroff: xroff.o dev.o font.o in.o cp.o tr.o ren.o out.o reg.o sbuf.o adj.o eval.o line.o
+xroff: xroff.o dev.o font.o in.o cp.o tr.o ren.o out.o reg.o sbuf.o adj.o eval.o line.o wb.o
 	$(CC) -o $@ $^ $(LDFLAGS)
 clean:
 	rm -f *.o xroff
--- a/adj.c
+++ b/adj.c
@@ -1,4 +1,3 @@
-#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -7,8 +6,7 @@
 #define ADJ_LLEN(a)	MAX(0, (a)->ll - ((a)->lt >= 0 ? (a)->lt : (a)->li))
 
 struct word {
-	int beg;	/* word beginning offset */
-	int end;	/* word ending offset */
+	struct sbuf s;
 	int wid;	/* word width */
 	int gap;	/* the space before this word */
 	int els_neg;	/* pre-extra line space */
@@ -16,12 +14,9 @@
 };
 
 struct adj {
-	char buf[LNLEN];		/* line buffer */
-	int len;
 	struct word words[NWORDS];	/* words in buf */
 	int nwords;
 	int wid;			/* total width of buf */
-	struct word *word;		/* current word */
 	int swid;			/* current space width */
 	int gap;			/* space before the next word */
 	int nls;			/* newlines before the next word */
@@ -67,7 +62,7 @@
 		return a->nls;
 	if (adj_fullnf(a))
 		return 1;
-	return !a->word && a->wid > ADJ_LLEN(a);
+	return a->wid > ADJ_LLEN(a);
 }
 
 /* is the adjustment buffer empty? */
@@ -83,10 +78,9 @@
 }
 
 /* move n words from the adjustment buffer to s */
-static int adj_move(struct adj *a, int n, char *s, int *els_neg, int *els_pos)
+static int adj_move(struct adj *a, int n, struct sbuf *s, int *els_neg, int *els_pos)
 {
 	struct word *cur;
-	int lendiff;
 	int w = 0;
 	int i;
 	*els_neg = 0;
@@ -93,9 +87,9 @@
 	*els_pos = 0;
 	for (i = 0; i < n; i++) {
 		cur = &a->words[i];
-		s += sprintf(s, "\\h'%du'", cur->gap);
-		memcpy(s, a->buf + cur->beg, cur->end - cur->beg);
-		s += cur->end - cur->beg;
+		sbuf_printf(s, "\\h'%du'", cur->gap);
+		sbuf_append(s, sbuf_buf(&cur->s));
+		sbuf_done(&cur->s);
 		w += cur->wid + cur->gap;
 		if (cur->els_neg < *els_neg)
 			*els_neg = cur->els_neg;
@@ -102,19 +96,11 @@
 		if (cur->els_pos > *els_pos)
 			*els_pos = cur->els_pos;
 	}
-	*s = '\0';
 	if (!n)
 		return 0;
-	lendiff = n < a->nwords ? a->words[n].beg : a->len;
-	memmove(a->buf, a->buf + lendiff, a->len - lendiff + 1);
-	a->len -= lendiff;
 	a->nwords -= n;
 	memmove(a->words, a->words + n, a->nwords * sizeof(a->words[0]));
 	a->wid -= w;
-	for (i = 0; i < a->nwords; i++) {
-		a->words[i].beg -= lendiff;
-		a->words[i].end -= lendiff;
-	}
 	if (a->nwords)		/* apply the new .l and .i */
 		adj_confupdate(a);
 	return w;
@@ -121,7 +107,7 @@
 }
 
 /* fill and copy a line into s */
-int adj_fill(struct adj *a, int ad_b, int fill, char *s,
+int adj_fill(struct adj *a, int ad_b, int fill, struct sbuf *s,
 		int *ll, int *in, int *ti, int *els_neg, int *els_pos)
 {
 	int adj_div, adj_rem;
@@ -154,66 +140,41 @@
 	return w;
 }
 
-static void adj_wordbeg(struct adj *adj, int gap)
+void adj_sp(struct adj *adj)
 {
-	adj->word = &adj->words[adj->nwords++];
-	adj->word->beg = adj->len;
-	adj->word->wid = 0;
-	adj->word->gap = gap;
-	adj->wid += gap;
-	adj->word->els_neg = 0;
-	adj->word->els_pos = 0;
+	adj->gap += adj->swid;
 }
 
-static void adj_wordend(struct adj *adj)
+void adj_nl(struct adj *adj)
 {
-	if (adj->word)
-		adj->word->end = adj->len;
-	adj->word = NULL;
+	adj->nls++;
+	adj->gap = 0;
 }
 
-/* insert s into the adjustment buffer */
-void adj_put(struct adj *adj, int wid, char *s, ...)
+static void adj_word(struct adj *adj, struct wb *wb)
 {
-	va_list ap;
-	if (!strcmp(s, " ")) {
-		adj_wordend(adj);
-		adj->gap += wid;
-		adj->swid = wid;
-		return;
-	}
-	if (!strcmp(s, "\n")) {
-		adj_wordend(adj);
-		adj->nls++;
-		adj->gap = 0;
-		adj->swid = wid;
-		return;
-	}
-	if (!adj->nwords)	/* apply the new .l and .i */
-		adj_confupdate(adj);
-	if (!adj->word) {
-		if (adj->nls && !adj->gap && adj->nwords >= 1)
-			adj->gap = adj->swid;
-		adj_wordbeg(adj, adj->gap);
-		adj->nls = 0;
-		adj->gap = 0;
-	}
-	va_start(ap, s);
-	adj->len += vsprintf(adj->buf + adj->len, s, ap);
-	va_end(ap);
-	adj->word->wid += wid;
-	adj->wid += wid;
+	struct word *cur = &adj->words[adj->nwords++];
+	cur->wid = wb_wid(wb);
+	cur->gap = adj->gap;
+	adj->wid += cur->wid + adj->gap;
+	wb_getels(wb, &cur->els_neg, &cur->els_pos);
+	sbuf_init(&cur->s);
+	sbuf_append(&cur->s, sbuf_buf(&wb->sbuf));
+	wb_reset(wb);
 }
 
-/* extra line-space requests */
-void adj_els(struct adj *adj, int els)
+/* insert wb into the adjustment buffer */
+void adj_wb(struct adj *adj, struct wb *wb)
 {
-	if (!adj->word)
-		adj_put(adj, 0, "");
-	if (els < adj->word->els_neg)
-		adj->word->els_neg = els;
-	if (els > adj->word->els_pos)
-		adj->word->els_pos = els;
+	if (wb_empty(wb) || adj->nwords == NWORDS)
+		return;
+	if (!adj->nwords)	/* apply the new .l and .i */
+		adj_confupdate(adj);
+	if (adj->nls && !adj->gap && adj->nwords >= 1)
+		adj->gap = adj->swid;
+	adj_word(adj, wb);
+	adj->nls = 0;
+	adj->gap = 0;
 }
 
 struct adj *adj_alloc(void)
--- a/line.c
+++ b/line.c
@@ -1,11 +1,32 @@
+#include <ctype.h>
 #include <stdlib.h>
 #include <string.h>
 #include "xroff.h"
 
-/* horizontal and vertical line characters */
-static char *hs[] = {"_", "\\_", "\\-", "\\(ru", "\\(ul", "\\(rn", NULL};
-static char *vs[] = {"\\(bv", "\\(br", "|", NULL};
+static char *ln_s;
 
+static int ln_next(void)
+{
+	return *ln_s ? (unsigned char) *ln_s++ : -1;
+}
+
+static void ln_back(int c)
+{
+	ln_s--;
+}
+
+static char *ln_push(char *s)
+{
+	char *old_s = ln_s;
+	ln_s = s;
+	return old_s;
+}
+
+static void ln_pop(char *s)
+{
+	ln_s = s;
+}
+
 static int cwid(char *c)
 {
 	struct glyph *g = dev_glyph(c, n_f);
@@ -12,6 +33,10 @@
 	return charwid(g ? g->wid : SC_DW, n_s);
 }
 
+/* horizontal and vertical line characters */
+static char *hs[] = {"_", "\\_", "\\-", "\\(ru", "\\(ul", "\\(rn", NULL};
+static char *vs[] = {"\\(bv", "\\(br", "|", NULL};
+
 static int lchar(char *c, char **cs)
 {
 	while (*cs)
@@ -20,18 +45,8 @@
 	return 0;
 }
 
-static void vmov(struct adj *adj, int w)
+void ren_hline(struct wb *wb, char *arg)
 {
-	adj_put(adj, w, "\\v'%du'", w);
-}
-
-static void hmov(struct adj *adj, int w)
-{
-	adj_put(adj, w, "\\h'%du'", w);
-}
-
-void ren_hline(struct adj *adj, char *arg)
-{
 	char *lc = "\\(ru";
 	int w, l, n, i, rem;
 	l = eval_up(&arg, 'm');
@@ -44,7 +59,7 @@
 	w = cwid(lc);
 	/* negative length; moving backwards */
 	if (l < 0) {
-		hmov(adj, l);
+		wb_hmov(wb, l);
 		l = -l;
 	}
 	n = l / w;
@@ -53,25 +68,25 @@
 	if (l < w) {
 		n = 1;
 		rem = 0;
-		hmov(adj, -(w - l) / 2);
+		wb_hmov(wb, -(w - l) / 2);
 	}
 	/* the initial gap */
 	if (rem) {
 		if (lchar(lc, hs)) {
-			adj_put(adj, w, "%s", lc);
-			hmov(adj, rem - w);
+			wb_put(wb, lc);
+			wb_hmov(wb, rem - w);
 		} else {
-			hmov(adj, rem);
+			wb_hmov(wb, rem);
 		}
 	}
 	for (i = 0; i < n; i++)
-		adj_put(adj, w, lc);
+		wb_put(wb, lc);
 	/* moving back */
 	if (l < w)
-		hmov(adj, -(w - l + 1) / 2);
+		wb_hmov(wb, -(w - l + 1) / 2);
 }
 
-void ren_vline(struct adj *adj, char *arg)
+void ren_vline(struct wb *wb, char *arg)
 {
 	char *lc = "\\(br";
 	int w, l, n, i, rem, hw, neg;
@@ -87,7 +102,7 @@
 	hw = cwid(lc);		/* character width */
 	/* negative length; moving backwards */
 	if (l < 0) {
-		vmov(adj, l);
+		wb_vmov(wb, l);
 		l = -l;
 	}
 	n = l / w;
@@ -96,98 +111,132 @@
 	if (l < w) {
 		n = 1;
 		rem = 0;
-		vmov(adj, -w + l / 2);
+		wb_vmov(wb, -w + l / 2);
 	}
 	/* the initial gap */
 	if (rem) {
 		if (lchar(lc, vs)) {
-			vmov(adj, w);
-			adj_put(adj, hw, "%s", lc);
-			hmov(adj, -hw);
-			vmov(adj, rem - w);
+			wb_vmov(wb, w);
+			wb_put(wb, lc);
+			wb_hmov(wb, -hw);
+			wb_vmov(wb, rem - w);
 		} else {
-			vmov(adj, rem);
+			wb_vmov(wb, rem);
 		}
 	}
 	for (i = 0; i < n; i++) {
-		vmov(adj, w);
-		adj_put(adj, hw, lc);
-		hmov(adj, -hw);
+		wb_vmov(wb, w);
+		wb_put(wb, lc);
+		wb_hmov(wb, -hw);
 	}
 	/* moving back */
 	if (l < w)
-		vmov(adj, l / 2);
+		wb_vmov(wb, l / 2);
 	if (neg)
-		vmov(adj, -l);
-	hmov(adj, hw);
+		wb_vmov(wb, -l);
+	wb_hmov(wb, hw);
 }
 
-static char *cutchar(char *d, char *s)
+void ren_bracket(struct wb *wb, char *arg)
 {
-	s = utf8get(d, s);
-	if (d[0] == '\\') {
-		s = utf8get(d + 1, s);
-		if (d[1] == '(') {
-			s = utf8get(d + 2, s);
-			s = utf8get(d + strlen(d), s);
-		}
+	struct wb wb2;
+	int n = 0, w = 0;
+	int c, center;
+	char *ln_prev = ln_push(arg);
+	wb_init(&wb2);
+	c = ln_next();
+	while (c >= 0) {
+		ln_back(c);
+		ren_char(&wb2, ln_next, ln_back);
+		if (wb_wid(&wb2) > w)
+			w = wb_wid(&wb2);
+		wb_hmov(&wb2, -wb_wid(&wb2));
+		wb_vmov(&wb2, SC_HT);
+		n++;
+		c = ln_next();
 	}
-	return s;
+	ln_pop(ln_prev);
+	center = -(n * SC_HT + SC_EM) / 2;
+	wb_vmov(wb, center + SC_HT);
+	wb_cat(wb, &wb2);
+	wb_done(&wb2);
+	wb_vmov(wb, center);
+	wb_hmov(wb, w);
 }
 
-static int maxwid(char *s)
+void ren_over(struct wb *wb, char *arg)
 {
-	char c[GNLEN * 4];
-	int w = 0;
-	while (*s) {
-		s = cutchar(c, s);
-		if (cwid(c) > w)
-			w = cwid(c);
+	struct wb wb2, wb3;
+	int w = 0, wc;
+	int c;
+	char *ln_prev = ln_push(arg);
+	wb_init(&wb2);
+	wb_init(&wb3);
+	c = ln_next();
+	while (c >= 0) {
+		ln_back(c);
+		ren_char(&wb3, ln_next, ln_back);
+		wc = wb_wid(&wb3);
+		if (wc > w)
+			w = wc;
+		wb_hmov(&wb2, -wc / 2);
+		wb_cat(&wb2, &wb3);
+		wb_hmov(&wb2, -wc / 2);
+		c = ln_next();
 	}
-	return w;
+	ln_pop(ln_prev);
+	wb_hmov(wb, w / 2);
+	wb_cat(wb, &wb2);
+	wb_hmov(wb, w / 2);
+	wb_done(&wb3);
+	wb_done(&wb2);
 }
 
-static int nchars(char *s)
+static int tok_num(char **s, int scale)
 {
-	char c[GNLEN * 4];
-	int n = 0;
-	while (*s) {
-		s = cutchar(c, s);
-		n++;
-	}
-	return n;
+	char tok[ILNLEN];
+	char *d = tok;
+	while (isspace(**s))
+		(*s)++;
+	while (**s && !isspace(**s))
+		*d++ = *(*s)++;
+	*d = '\0';
+	return eval(tok, scale);
 }
 
-void ren_bracket(struct adj *adj, char *arg)
+void ren_draw(struct wb *wb, char *s)
 {
-	char c[GNLEN * 4];
-	int ht, hc;
-	int w = maxwid(arg);
-	int n = nchars(arg);
-	ht = n * SC_HT;
-	hc = -(ht + SC_EM) / 2;
-	vmov(adj, hc + SC_HT);
-	while (*arg) {
-		arg = cutchar(c, arg);
-		adj_put(adj, cwid(arg), c);
-		hmov(adj, -cwid(c));
-		vmov(adj, SC_HT);
+	int h1, h2, v1, v2;
+	int c = *s++;
+	switch (c) {
+	case 'l':
+		h1 = tok_num(&s, 'm');
+		v1 = tok_num(&s, 'v');
+		wb_drawl(wb, h1, v1);
+		break;
+	case 'c':
+		h1 = tok_num(&s, 'm');
+		wb_drawc(wb, h1);
+		break;
+	case 'e':
+		h1 = tok_num(&s, 'm');
+		v1 = tok_num(&s, 'v');
+		wb_drawe(wb, h1, v1);
+		break;
+	case 'a':
+		h1 = tok_num(&s, 'm');
+		v1 = tok_num(&s, 'v');
+		h2 = tok_num(&s, 'm');
+		v2 = tok_num(&s, 'v');
+		wb_drawa(wb, h1, v1, h2, v2);
+		break;
+	default:
+		wb_drawxbeg(wb, c);
+		while (*s) {
+			h1 = tok_num(&s, 'm');
+			v1 = tok_num(&s, 'v');
+			wb_drawxdot(wb, h1, v1);
+		}
+		wb_drawxend(wb);
 	}
-	hmov(adj, w);
-	vmov(adj, hc);
-}
-
-void ren_over(struct adj *adj, char *arg)
-{
-	char c[GNLEN * 4];
-	int a;
-	int w = maxwid(arg);
-	while (*arg) {
-		arg = cutchar(c, arg);
-		a = (w - cwid(c) + 1) / 2;
-		hmov(adj, a);
-		adj_put(adj, cwid(arg), c);
-		hmov(adj, -cwid(c) - a);
-	}
-	hmov(adj, w);
 }
--- a/out.c
+++ b/out.c
@@ -110,84 +110,51 @@
 	return s;
 }
 
-static char *tok_str(char *d, char *s)
+static int tok_num(char **s, int scale)
 {
-	while (isspace(*s))
-		s++;
-	while (*s && !isspace(*s))
-		*d++ = *s++;
+	char tok[ILNLEN];
+	char *d = tok;
+	while (isspace(**s))
+		(*s)++;
+	while (**s && !isspace(**s))
+		*d++ = *(*s)++;
 	*d = '\0';
-	return s;
+	return eval(tok, scale);
 }
 
-static char *tok_num(int *d, char *s, char **cc, int scale)
+static void out_draw(char *s)
 {
-	char tok[ILNLEN];
-	s = tok_str(tok, s);
-	*d = eval(tok, scale);
-	if (*cc)
-		*cc += sprintf(*cc, " %du", *d);
-	else
-		outnn(" %d", *d);
-	return s;
-}
-
-/* parse \D arguments and copy them into cc; return the width */
-int out_draw(char *s, char *cc)
-{
-	int h1, h2, v1, v2;
-	int hd = 0, vd = 0;
 	int c = *s++;
-	if (cc)
-		*cc++ = c;
-	else
-		out("D%c", c);
+	out("D%c", c);
 	switch (c) {
 	case 'l':
-		s = tok_num(&h1, s, &cc, 'm');
-		s = tok_num(&v1, s, &cc, 'v');
-		if (!cc)			/* dpost requires this */
-			outnn(" .");
-		hd = h1;
-		vd = v1;
+		outnn(" %d", tok_num(&s, 'm'));
+		outnn(" %d", tok_num(&s, 'v'));
+		outnn(" .");			/* dpost requires this */
 		break;
 	case 'c':
-		s = tok_num(&h1, s, &cc, 'm');
-		hd = h1;
-		vd = 0;
+		outnn(" %d", tok_num(&s, 'm'));
 		break;
 	case 'e':
-		s = tok_num(&h1, s, &cc, 'm');
-		s = tok_num(&v1, s, &cc, 'v');
-		hd = h1;
-		vd = 0;
+		outnn(" %d", tok_num(&s, 'm'));
+		outnn(" %d", tok_num(&s, 'v'));
 		break;
 	case 'a':
-		s = tok_num(&h1, s, &cc, 'm');
-		s = tok_num(&v1, s, &cc, 'v');
-		s = tok_num(&h2, s, &cc, 'm');
-		s = tok_num(&v2, s, &cc, 'v');
-		hd = h1 + h2;
-		vd = v1 + v2;
+		outnn(" %d", tok_num(&s, 'm'));
+		outnn(" %d", tok_num(&s, 'v'));
+		outnn(" %d", tok_num(&s, 'm'));
+		outnn(" %d", tok_num(&s, 'v'));
 		break;
 	default:
-		s = tok_num(&h1, s, &cc, 'm');
-		s = tok_num(&v1, s, &cc, 'v');
-		hd = h1;
-		vd = v1;
+		outnn(" %d", tok_num(&s, 'm'));
+		outnn(" %d", tok_num(&s, 'v'));
 		while (*s) {
-			s = tok_num(&h2, s, &cc, 'm');
-			s = tok_num(&v2, s, &cc, 'v');
-			hd += h2;
-			vd += v2;
+			outnn(" %d", tok_num(&s, 'm'));
+			outnn(" %d", tok_num(&s, 'v'));
 		}
 		break;
 	}
-	if (cc)
-		*cc = '\0';
-	else
-		outnn("\n");
-	return hd;
+	outnn("\n");
 }
 
 void out_line(char *s)
@@ -207,7 +174,7 @@
 			} else if (strchr("DfhsvX", c[1])) {
 				s = escarg(s, arg, c[1]);
 				if (c[1] == 'D') {
-					out_draw(arg, NULL);
+					out_draw(arg);
 					continue;
 				}
 				if (c[1] == 'f') {
--- a/ren.c
+++ b/ren.c
@@ -20,12 +20,10 @@
 };
 static struct div divs[NPREV];	/* diversion stack */
 static struct div *cdiv;	/* current diversion */
-static int ren_f = -1;		/* last rendered n_f */
-static int ren_s = -1;		/* last rendered n_s */
 static int ren_div;		/* rendering a diversion */
-static int ren_part;		/* partial line (\c) */
 
 static int ren_backed = -1;	/* pushed back character */
+static struct wb ren_wb;	/* the main ren.c word buffer */
 
 static int bp_first = 1;	/* prior to the first page */
 static int bp_next = 1;		/* next page number */
@@ -54,8 +52,6 @@
 		if (args[0][2] == 'a' && str_get(cdiv->reg))	/* .da */
 			sbuf_append(&cdiv->sbuf, str_get(cdiv->reg));
 		sbuf_append(&cdiv->sbuf, DIV_BEG "\n");
-		ren_f = -1;
-		ren_s = -1;
 		cdiv->prev_d = n_d;
 		cdiv->prev_h = n_h;
 		cdiv->prev_mk = n_mk;
@@ -76,8 +72,6 @@
 		n_mk = cdiv->prev_mk;
 		n_ns = cdiv->prev_ns;
 		cdiv = cdiv > divs ? cdiv - 1 : NULL;
-		ren_f = -1;
-		ren_s = -1;
 	}
 }
 
@@ -88,7 +82,7 @@
 
 int f_hpos(void)
 {
-	return adj_wid(cadj);
+	return adj_wid(cadj) + wb_wid(&ren_wb);
 }
 
 void tr_divbeg(char **args)
@@ -223,12 +217,13 @@
 /* return 1 if triggered a trap */
 static int ren_bradj(struct adj *adj, int fill, int ad)
 {
-	char buf[LNLEN];
+	struct sbuf sbuf;
 	int ll, li, lt, els_neg, els_pos;
 	int w, prev_d;
 	ren_first();
 	if (!adj_empty(adj, fill)) {
-		w = adj_fill(adj, ad == AD_B, fill, buf,
+		sbuf_init(&sbuf);
+		w = adj_fill(adj, ad == AD_B, fill, &sbuf,
 				&ll, &li, &lt, &els_neg, &els_pos);
 		prev_d = n_d;
 		if (els_neg)
@@ -235,9 +230,10 @@
 			ren_sp(-els_neg);
 		if (!n_ns || w || els_neg || els_pos) {
 			ren_sp(0);
-			ren_line(buf, w, ad, ll, li, lt);
+			ren_line(sbuf_buf(&sbuf), w, ad, ll, li, lt);
 			n_ns = 0;
 		}
+		sbuf_done(&sbuf);
 		if (els_pos)
 			ren_sp(els_pos);
 		n_a = els_pos;
@@ -477,75 +473,70 @@
 	return l;
 }
 
-static void ren_cmd(struct adj *adj, int c, char *arg)
+static void ren_cmd(struct wb *wb, int c, char *arg)
 {
-	char draw_arg[ILNLEN];
 	struct glyph *g;
-	int n, w;
 	switch (c) {
 	case ' ':
-		w = charwid(dev_spacewid(), n_s);
-		adj_put(adj, w, "\\h'%du'", w);
+		wb_hmov(wb, charwid(dev_spacewid(), n_s));
 		break;
 	case 'b':
-		ren_bracket(adj, arg);
+		ren_bracket(wb, arg);
 		break;
+	case 'c':
+		wb_setpart(wb);
+		break;
 	case 'D':
-		w = out_draw(arg, draw_arg);
-		adj_put(adj, w, "\\D'%s'", draw_arg);
+		ren_draw(wb, arg);
 		break;
 	case 'd':
-		adj_put(adj, 0, "\\v'%du'", eval(".5m", 0));
+		wb_vmov(wb, SC_EM / 2);
 		break;
 	case 'f':
 		ren_ft(arg);
 		break;
 	case 'h':
-		n = eval(arg, 'm');
-		adj_put(adj, n, "\\h'%du'", n);
+		wb_hmov(wb, eval(arg, 'm'));
 		break;
 	case 'k':
 		num_set(REG(arg[0], arg[1]), f_hpos() - n_lb);
 		break;
 	case 'L':
-		ren_vline(adj, arg);
+		ren_vline(wb, arg);
 		break;
 	case 'l':
-		ren_hline(adj, arg);
+		ren_hline(wb, arg);
 		break;
 	case 'o':
-		ren_over(adj, arg);
+		ren_over(wb, arg);
 		break;
 	case 'r':
-		adj_put(adj, 0, "\\v'%du'", eval("-1m", 0));
+		wb_vmov(wb, -SC_EM);
 		break;
 	case 's':
 		ren_ps(arg);
 		break;
 	case 'u':
-		adj_put(adj, 0, "\\v'%du'", eval("-.5m", 0));
+		wb_vmov(wb, -SC_EM / 2);
 		break;
 	case 'v':
-		adj_put(adj, 0, "\\v'%du'", eval(arg, 'v'));
+		wb_vmov(wb, eval(arg, 'v'));
 		break;
 	case 'X':
-		adj_put(adj, 0, "\\X'%s'", arg);
+		wb_etc(wb, arg);
 		break;
 	case 'x':
-		adj_els(adj, eval(arg, 'v'));
+		wb_els(wb, eval(arg, 'v'));
 		break;
 	case '0':
 		g = dev_glyph("0", n_f);
-		w = charwid(g ? g->wid : SC_DW, n_s);
-		adj_put(adj, w, "\\h'%du'", w);
+		wb_hmov(wb, charwid(g ? g->wid : SC_DW, n_s));
 		break;
 	case '|':
-		w = eval("1m/6", 0);
-		adj_put(adj, w, "\\h'%du'", w);
+		wb_hmov(wb, SC_EM / 6);
 		break;
 	case '^':
-		w = eval("1m/12", 0);
-		adj_put(adj, w, "\\h'%du'", w);
+		wb_hmov(wb, SC_EM / 12);
 		break;
 	case '{':
 	case '}':
@@ -553,18 +544,16 @@
 	}
 }
 
-/* read one character and place it inside adj buffer */
-static int ren_char(struct adj *adj, int (*next)(void), void (*back)(int))
+/* read one character and place it inside wb buffer */
+void ren_char(struct wb *wb, int (*next)(void), void (*back)(int))
 {
 	char c[GNLEN * 4];
 	char arg[ILNLEN];
-	struct glyph *g;
-	int zerowid = 0;
 	int w;
 	nextchar(c, next);
 	if (c[0] == ' ' || c[0] == '\n') {
-		adj_put(adj, charwid(dev_spacewid(), n_s), c);
-		return 0;
+		wb_put(wb, c);
+		return;
 	}
 	if (c[0] == '\\') {
 		nextchar(c + 1, next);
@@ -573,40 +562,17 @@
 			l += nextchar(c + 2 + l, next);
 			c[2 + l] = '\0';
 		} else if (c[1] == 'z') {
-			zerowid = 1;
-			nextchar(c, next);
-			if (c[0] == '\\') {
-				nextchar(c + 1, next);
-				if (c[1] == '(') {
-					nextchar(c + 2, next);
-					nextchar(c + strlen(c), next);
-				}
-			}
-		} else if (c[1] == 'c') {
-			if (adj == cadj)
-				ren_part = 1;
-			return 0;
-		} else if (strchr(" bDdfhkLlorsuvXxz0^|{}&", c[1])) {
+			w = wb_wid(wb);
+			ren_char(wb, next, back);
+			wb_hmov(wb, w - wb_wid(wb));
+			return;
+		} else if (strchr(" bcDdfhkLlorsuvXxz0^|{}&", c[1])) {
 			escarg_ren(arg, c[1], next, back);
-			ren_cmd(adj, c[1], arg);
-			return 0;
+			ren_cmd(wb, c[1], arg);
+			return;
 		}
 	}
-	if (ren_s != n_s) {
-		adj_swid(adj, charwid(dev_spacewid(), n_s));
-		adj_put(adj, 0, "\\s(%02d", n_s);
-		ren_s = n_s;
-	}
-	if (ren_f != n_f) {
-		adj_put(adj, 0, "\\f(%02d", n_f);
-		ren_f = n_f;
-	}
-	g = dev_glyph(c, n_f);
-	w = charwid(g ? g->wid : SC_DW, n_s);
-	adj_put(adj, w, "%s", c);
-	if (zerowid)
-		adj_put(adj, -w, "\\h'%du'", -w);
-	return g ? g->type : 0;
+	wb_put(wb, c);
 }
 
 /* read the argument of \w and push its width */
@@ -613,11 +579,10 @@
 int ren_wid(int (*next)(void), void (*back)(int))
 {
 	char delim[GNLEN];
-	struct adj *adj = adj_alloc();
 	int c, n;
-	int type = 0;
+	struct wb wb;
+	wb_init(&wb);
 	schar_read(delim, next);
-	adj_ll(adj, n_l);
 	odiv_beg();
 	c = next();
 	while (c >= 0 && c != '\n') {
@@ -624,19 +589,17 @@
 		back(c);
 		if (!schar_jump(delim, next, back))
 			break;
-		type |= ren_char(adj, next, back);
+		ren_char(&wb, next, back);
 		c = next();
 	}
 	odiv_end();
-	ren_f = -1;
-	ren_s = -1;
-	n = adj_wid(adj);
-	n_ct = type;
-	adj_free(adj);
+	n = wb_wid(&wb);
+	wb_wconf(&wb, &n_ct, &n_st, &n_sb);
+	wb_done(&wb);
 	return n;
 }
 
-static void ren_until(struct adj *adj, char *delim, int (*next)(void), void (*back)(int))
+static void ren_until(struct wb *wb, char *delim, int (*next)(void), void (*back)(int))
 {
 	int c;
 	c = next();
@@ -644,7 +607,7 @@
 		back(c);
 		if (!schar_jump(delim, next, back))
 			break;
-		ren_char(adj, next, back);
+		ren_char(wb, next, back);
 		c = next();
 	}
 	if (c == '\n')
@@ -651,67 +614,71 @@
 		back(c);
 }
 
-static void adj_cpy(struct adj *dst, struct adj *src, int left)
+static void wb_cpy(struct wb *dst, struct wb *src, int left)
 {
-	char buf[LNLEN];
-	int ll, li, lt, els_neg, els_pos;
-	int w;
-	adj_put(src, 0, "\n");
-	w = adj_fill(src, 0, 0, buf, &ll, &li, &lt, &els_neg, &els_pos);
-	adj_put(dst, left - adj_wid(dst), "\\h'%du'", left - adj_wid(dst));
-	adj_put(dst, w, "%s", buf);
-	adj_els(dst, els_neg);
-	adj_els(dst, els_pos);
+	wb_hmov(dst, left - wb_wid(dst));
+	wb_cat(dst, src);
 }
 
 void ren_tl(int (*next)(void), void (*back)(int))
 {
-	struct adj *adj = adj_alloc();
-	struct adj *tmp = adj_alloc();
+	struct adj *adj;
+	struct wb wb, wb2;
 	char delim[GNLEN];
-	adj_ll(tmp, n_lt);
-	adj_ll(adj, n_lt);
+	adj = adj_alloc();
+	wb_init(&wb);
+	wb_init(&wb2);
 	schar_read(delim, next);
 	/* the left-adjusted string */
-	ren_until(adj, delim, next, back);
+	ren_until(&wb2, delim, next, back);
+	wb_cpy(&wb, &wb2, 0);
 	/* the centered string */
-	ren_until(tmp, delim, next, back);
-	adj_cpy(adj, tmp, (n_lt - adj_wid(tmp)) / 2);
+	ren_until(&wb2, delim, next, back);
+	wb_cpy(&wb, &wb2, (n_lt - wb_wid(&wb2)) / 2);
 	/* the right-adjusted string */
-	ren_until(tmp, delim, next, back);
-	adj_cpy(adj, tmp, n_lt - adj_wid(tmp));
+	ren_until(&wb2, delim, next, back);
+	wb_cpy(&wb, &wb2, n_lt - wb_wid(&wb2));
 	/* flushing the line */
-	adj_put(adj, 0, "\n");
+	adj_ll(adj, n_lt);
+	adj_wb(adj, &wb);
+	adj_nl(adj);
 	ren_bradj(adj, 0, AD_L);
-	adj_free(tmp);
 	adj_free(adj);
+	wb_done(&wb2);
+	wb_done(&wb);
 }
 
 /* read characters from in.c and pass rendered lines to out.c */
 void render(void)
 {
+	struct wb *wb = &ren_wb;
 	int c;
 	n_nl = -1;
 	tr_first();
 	ren_first();			/* transition to the first page */
 	c = ren_next();
+	wb_init(wb);
 	while (c >= 0) {
-		if (!ren_part && (c == ' ' || c == '\n')) {
-			ren_back(c);
-			ren_char(cadj, ren_next, ren_back);
+		if (c == ' ' || c == '\n') {
+			adj_swid(cadj, charwid(dev_spacewid(), n_s));
+			adj_wb(cadj, wb);
+			if (!wb_part(wb)) {
+				if (c == '\n')
+					adj_nl(cadj);
+				else
+					adj_sp(cadj);
+			}
 		}
 		while (adj_full(cadj, !n_ce && n_u))
 			ren_br(0);
 		if (c == '\n')		/* end of input line */
 			n_lb = adj_wid(cadj);
-		if (c == '\n' && !ren_part)
+		if (c == '\n' && !wb_part(wb))
 			n_ce = MAX(0, n_ce - 1);
-		if (!ren_part && (c != ' ' && c != '\n')) {
+		if (c != ' ') {
 			ren_back(c);
-			ren_char(cadj, ren_next, ren_back);
+			ren_char(wb, ren_next, ren_back);
 		}
-		if (ren_part && c == '\n')
-			ren_part = 0;
 		c = ren_next();
 	}
 	ren_br(1);
--- a/sbuf.c
+++ b/sbuf.c
@@ -1,3 +1,5 @@
+#include <stdarg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include "xroff.h"
@@ -33,6 +35,16 @@
 		sbuf_extend(sbuf, sbuf->n + len + 1);
 	memcpy(sbuf->s + sbuf->n, s, len);
 	sbuf->n += len;
+}
+
+void sbuf_printf(struct sbuf *sbuf, char *s, ...)
+{
+	char buf[ILNLEN];
+	va_list ap;
+	va_start(ap, s);
+	vsprintf(buf, s, ap);
+	va_end(ap);
+	sbuf_append(sbuf, buf);
 }
 
 void sbuf_putnl(struct sbuf *sbuf)
--- /dev/null
+++ b/wb.c
@@ -1,0 +1,201 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "xroff.h"
+
+void wb_init(struct wb *wb)
+{
+	memset(wb, 0, sizeof(*wb));
+	sbuf_init(&wb->sbuf);
+	wb->f = -1;
+	wb->s = -1;
+}
+
+void wb_done(struct wb *wb)
+{
+	sbuf_done(&wb->sbuf);
+}
+
+/* update wb->st and wb->sb */
+static void wb_stsb(struct wb *wb)
+{
+	wb->st = MIN(wb->st, wb->v - SC_HT);
+	wb->sb = MAX(wb->sb, wb->v);
+}
+
+/* append font and size to the buffer if needed */
+static void wb_font(struct wb *wb)
+{
+	if (wb->f != n_f) {
+		sbuf_printf(&wb->sbuf, "\\f(%02d", n_f);
+		wb->f = n_f;
+	}
+	if (wb->s != n_s) {
+		sbuf_printf(&wb->sbuf, "\\s(%02d", n_s);
+		wb->s = n_s;
+	}
+	wb_stsb(wb);
+}
+
+void wb_hmov(struct wb *wb, int n)
+{
+	wb->h += n;
+	sbuf_printf(&wb->sbuf, "\\h'%du'", n);
+}
+
+void wb_vmov(struct wb *wb, int n)
+{
+	wb->v += n;
+	sbuf_printf(&wb->sbuf, "\\v'%du'", n);
+}
+
+void wb_els(struct wb *wb, int els)
+{
+	if (els > wb->els_pos)
+		wb->els_pos = els;
+	if (els < wb->els_neg)
+		wb->els_neg = els;
+}
+
+void wb_etc(struct wb *wb, char *x)
+{
+	wb_font(wb);
+	sbuf_printf(&wb->sbuf, "\\X%s", x);
+}
+
+void wb_put(struct wb *wb, char *c)
+{
+	struct glyph *g;
+	if (c[0] == '\n') {
+		wb->part = 0;
+		return;
+	}
+	if (c[0] == ' ') {
+		wb_hmov(wb, charwid(dev_spacewid(), n_s));
+		return;
+	}
+	g = dev_glyph(c, n_f);
+	wb_font(wb);
+	sbuf_append(&wb->sbuf, c);
+	wb->h += charwid(g ? g->wid : SC_DW, n_s);
+	wb->ct |= g ? g->type : 0;
+	wb_stsb(wb);
+}
+
+int wb_part(struct wb *wb)
+{
+	return wb->part;
+}
+
+void wb_setpart(struct wb *wb)
+{
+	wb->part = 1;
+}
+
+void wb_drawl(struct wb *wb, int h, int v)
+{
+	wb_font(wb);
+	sbuf_printf(&wb->sbuf, "\\D'l %du %du'", h, v);
+	wb->h += h;
+	wb->v += v;
+	wb_stsb(wb);
+}
+
+void wb_drawc(struct wb *wb, int r)
+{
+	wb_font(wb);
+	sbuf_printf(&wb->sbuf, "\\D'c %du'", r);
+	wb->h += r;
+}
+
+void wb_drawe(struct wb *wb, int h, int v)
+{
+	wb_font(wb);
+	sbuf_printf(&wb->sbuf, "\\D'e %du %du'", h, v);
+	wb->h += h;
+}
+
+void wb_drawa(struct wb *wb, int h1, int v1, int h2, int v2)
+{
+	wb_font(wb);
+	sbuf_printf(&wb->sbuf, "\\D'a %du %du %du %du'", h1, v1, h2, v2);
+	wb->h += h1 + h2;
+	wb->v += v1 + v2;
+	wb_stsb(wb);
+}
+
+void wb_drawxbeg(struct wb *wb, int c)
+{
+	wb_font(wb);
+	sbuf_printf(&wb->sbuf, "\\D'%c", c);
+}
+
+void wb_drawxdot(struct wb *wb, int h, int v)
+{
+	sbuf_printf(&wb->sbuf, " %du %du", h, v);
+	wb->h += h;
+	wb->v += v;
+	wb_stsb(wb);
+}
+
+void wb_drawxend(struct wb *wb)
+{
+	sbuf_printf(&wb->sbuf, "'");
+}
+
+void wb_reset(struct wb *wb)
+{
+	sbuf_done(&wb->sbuf);
+	sbuf_init(&wb->sbuf);
+	wb->els_pos = 0;
+	wb->els_neg = 0;
+	wb->ct = 0;
+	wb->sb = 0;
+	wb->st = 0;
+	wb->h = 0;
+	wb->v = 0;
+	wb->f = -1;
+	wb->s = -1;
+}
+
+void wb_cat(struct wb *wb, struct wb *src)
+{
+	sbuf_append(&wb->sbuf, sbuf_buf(&src->sbuf));
+	if (src->f >= 0)
+		wb->f = src->f;
+	if (src->s >= 0)
+		wb->s = src->s;
+	wb_els(wb, src->els_neg);
+	wb_els(wb, src->els_pos);
+	if (src->part)
+		wb->part = src->part;
+	wb->ct |= src->ct;
+	wb->st = MIN(wb->st, wb->v + src->st);
+	wb->sb = MAX(wb->sb, wb->v + src->sb);
+	wb->h += src->h;
+	wb->v += src->v;
+	wb_reset(src);
+}
+
+int wb_wid(struct wb *wb)
+{
+	return wb->h;
+}
+
+int wb_empty(struct wb *wb)
+{
+	return sbuf_empty(&wb->sbuf);
+}
+
+void wb_getels(struct wb *wb, int *els_neg, int *els_pos)
+{
+	*els_neg = wb->els_neg;
+	*els_pos = wb->els_pos;
+}
+
+void wb_wconf(struct wb *wb, int *ct, int *st, int *sb)
+{
+	*ct = wb->ct;
+	*st = -wb->st;
+	*sb = -wb->sb;
+}
--- a/xroff.h
+++ b/xroff.h
@@ -14,7 +14,7 @@
 #define GNLEN		32	/* glyph name length */
 #define ILNLEN		256	/* line limit of input files */
 #define LNLEN		4000	/* line buffer length (ren.c/out.c) */
-#define NWORDS		1000	/* number of words in line buffer */
+#define NWORDS		256	/* number of words in line buffer */
 #define NARGS		9	/* number of macro arguments */
 #define RLEN		4	/* register/macro name */
 #define NPREV		16	/* environment stack depth */
@@ -25,7 +25,8 @@
 #define ESC_Q	"bCDhHlLNoSvwxX"	/* quoted escape sequences */
 #define ESC_P	"*fgkns"		/* 1 or 2-char escape sequences */
 
-#define MAX(a, b)	((a) > (b) ? (a) : (b))
+#define MIN(a, b)	((a) < (b) ? (a) : (b))
+#define MAX(a, b)	((a) < (b) ? (b) : (a))
 #define LEN(a)		(sizeof(a) / sizeof((a)[0]))
 
 /* number registers */
@@ -121,17 +122,88 @@
 #define cp_back		in_back	/* cp.c is stateless */
 void tr_first(void);		/* read until the first non-command line */
 
+/* variable length string buffer */
+struct sbuf {
+	char *s;
+	int sz;
+	int n;
+};
+
+void sbuf_init(struct sbuf *sbuf);
+void sbuf_done(struct sbuf *sbuf);
+char *sbuf_buf(struct sbuf *sbuf);
+void sbuf_add(struct sbuf *sbuf, int c);
+void sbuf_append(struct sbuf *sbuf, char *s);
+void sbuf_printf(struct sbuf *sbuf, char *s, ...);
+void sbuf_putnl(struct sbuf *sbuf);
+int sbuf_empty(struct sbuf *sbuf);
+
+/* word buffer */
+struct wb {
+	struct sbuf sbuf;
+	int f, s;		/* the last output font and size */
+	int part;		/* partial input (\c) */
+	int els_neg, els_pos;	/* extra line spacing */
+	int h, v;		/* current buffer vertical and horizontal positions */
+	int ct, sb, st;		/* \w registers */
+};
+
+void wb_init(struct wb *wb);
+void wb_done(struct wb *wb);
+void wb_reset(struct wb *wb);
+void wb_hmov(struct wb *wb, int n);
+void wb_vmov(struct wb *wb, int n);
+void wb_els(struct wb *wb, int els);
+void wb_etc(struct wb *wb, char *x);
+void wb_put(struct wb *wb, char *c);
+int wb_part(struct wb *wb);
+void wb_setpart(struct wb *wb);
+void wb_drawl(struct wb *wb, int h, int v);
+void wb_drawc(struct wb *wb, int r);
+void wb_drawe(struct wb *wb, int h, int v);
+void wb_drawa(struct wb *wb, int h1, int v1, int h2, int v2);
+void wb_drawxbeg(struct wb *wb, int c);
+void wb_drawxdot(struct wb *wb, int h, int v);
+void wb_drawxend(struct wb *wb);
+void wb_cat(struct wb *wb, struct wb *src);
+int wb_wid(struct wb *wb);
+int wb_empty(struct wb *wb);
+void wb_getels(struct wb *wb, int *els_neg, int *els_pos);
+void wb_wconf(struct wb *wb, int *ct, int *st, int *sb);
+
+/* adjustment */
+#define AD_L		0
+#define AD_B		1
+#define AD_C		3
+#define AD_R		5
+
+struct adj *adj_alloc(void);
+void adj_free(struct adj *adj);
+int adj_fill(struct adj *adj, int ad_b, int fill, struct sbuf *dst,
+		int *ll, int *in, int *ti, int *els_neg, int *els_pos);
+int adj_full(struct adj *adj, int fill);
+int adj_empty(struct adj *adj, int fill);
+int adj_wid(struct adj *adj);
+void adj_swid(struct adj *adj, int swid);
+void adj_ll(struct adj *adj, int ll);
+void adj_in(struct adj *adj, int in);
+void adj_ti(struct adj *adj, int ti);
+void adj_wb(struct adj *adj, struct wb *wb);
+void adj_nl(struct adj *adj);
+void adj_sp(struct adj *adj);
+
 /* rendering */
 void render(void);		/* read from in.c and print the output */
+void ren_char(struct wb *wb, int (*next)(void), void (*back)(int));
 int ren_wid(int (*next)(void), void (*back)(int));
 void ren_tl(int (*next)(void), void (*back)(int));
 void out_line(char *s);		/* output the given rendered line */
-int out_draw(char *s, char *cc);
 void out(char *s, ...);				/* output troff cmd */
-void ren_hline(struct adj *adj, char *arg);	/* horizontal line */
-void ren_vline(struct adj *adj, char *arg);	/* vertical line */
-void ren_bracket(struct adj *adj, char *arg);	/* \b */
-void ren_over(struct adj *adj, char *arg);	/* \o */
+void ren_hline(struct wb *wb, char *arg);	/* horizontal line */
+void ren_vline(struct wb *wb, char *arg);	/* vertical line */
+void ren_bracket(struct wb *wb, char *arg);	/* \b */
+void ren_over(struct wb *wb, char *arg);	/* \o */
+void ren_draw(struct wb *wb, char *arg);	/* \D */
 
 /* troff commands */
 void tr_bp(char **args);
@@ -171,46 +243,10 @@
 void schar_read(char *d, int (*next)(void));
 int schar_jump(char *d, int (*next)(void), void (*back)(int));
 
-/* variable length string buffer */
-struct sbuf {
-	char *s;
-	int sz;
-	int n;
-};
-
-void sbuf_init(struct sbuf *sbuf);
-void sbuf_done(struct sbuf *sbuf);
-char *sbuf_buf(struct sbuf *sbuf);
-void sbuf_add(struct sbuf *sbuf, int c);
-void sbuf_append(struct sbuf *sbuf, char *s);
-void sbuf_putnl(struct sbuf *sbuf);
-int sbuf_empty(struct sbuf *sbuf);
-
 /* diversions */
 #define DIV_BEG		".&<"
 #define DIV_END		".&>"
 
-/* adjustment */
-#define AD_L		0
-#define AD_B		1
-#define AD_C		3
-#define AD_R		5
-
-struct adj *adj_alloc(void);
-void adj_free(struct adj *adj);
-int adj_fill(struct adj *adj, int ad_b, int fill, char *dst,
-		int *ll, int *in, int *ti, int *els_neg, int *els_pos);
-void adj_put(struct adj *adj, int wid, char *s, ...);
-void adj_swid(struct adj *adj, int swid);
-int adj_full(struct adj *adj, int fill);
-int adj_empty(struct adj *adj, int fill);
-int adj_wid(struct adj *adj);
-void adj_ll(struct adj *adj, int ll);
-void adj_in(struct adj *adj, int in);
-void adj_ti(struct adj *adj, int ti);
-void adj_els(struct adj *adj, int els);
-void adj_conf(struct adj *adj, int *ll, int *in, int *ti);
-
 /* builtin number registers; n_X for .X register */
 #define REG(c1, c2)	((c1) * 256 + (c2))
 #define n_a		(*nreg(REG('.', 'a')))
@@ -231,6 +267,8 @@
 #define n_dl		(*nreg(REG('d', 'l')))
 #define n_dn		(*nreg(REG('d', 'n')))
 #define n_nl		(*nreg(REG('n', 'l')))
+#define n_sb		(*nreg(REG('s', 'b')))
+#define n_st		(*nreg(REG('s', 't')))
 #define n_pg		(*nreg(REG('%', '\0')))	/* % */
 #define n_lb		(*nreg(REG(0, 'b')))	/* input line beg */
 #define n_ce		(*nreg(REG(0, 'c')))	/* .ce remaining */