shithub: neatroff

Download patch

ref: d81da34389f192fee51865798fc86ff7e521617a
parent: 5d3333450b2456279531d67e9ca11ce08446da9a
author: Ali Gholami Rudi <ali@rudi.ir>
date: Wed May 15 17:45:14 EDT 2013

dir: support text direction with .>>, .<<, \> and \<

The following troff requests specify text direction:

* .<< and .>> change the direction of text (.<< for r2l and .>>
  to l2r); the default direction is l2r.
* \< and \> change text direction temporarily.

Text direction processing starts after the first invocation
of .>> or .<<.

As an example, one can write:

  .<<
  This is a paragraph in a right-to-left language
  with some embedded \>left-to-right words\< in it.
  .>>

The current text direction and temporary direction are available
through .td and .cd number registers respectively; 0 for l2r and 1 for
r2l.

To indent the right side of output lines, two new requests have been
added: .in2 and .ti2 are equivalent to .in and .ti but for the right
side.  The .I number register stores the current right-side
indentation.

--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@
 CFLAGS = -Wall -O2 "-DTROFFFDIR=\"$(FDIR)\"" "-DTROFFMDIR=\"$(MDIR)\""
 LDFLAGS =
 OBJS = roff.o dev.o font.o in.o cp.o tr.o ren.o out.o reg.o sbuf.o fmt.o \
-	eval.o draw.o wb.o hyph.o map.o clr.o char.o dict.o iset.o
+	eval.o draw.o wb.o hyph.o map.o clr.o char.o dict.o iset.o dir.o
 
 all: roff
 %.o: %.c roff.h
--- a/char.c
+++ b/char.c
@@ -253,7 +253,7 @@
 			*r = '\0';
 			if (**s == ']')
 				(*s)++;
-		} else if (strchr("CDfhmsvXx", r[1])) {
+		} else if (strchr("CDfhmsvXx<>", r[1])) {
 			int c = r[1];
 			r[0] = '\0';
 			if (strchr(ESC_P, c))
--- /dev/null
+++ b/dir.c
@@ -1,0 +1,161 @@
+/* output text direction */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "roff.h"
+
+int dir_do;			/* enable text direction processing */
+
+static char *dbuf;		/* text in n_td direction */
+static int dbuf_sz, dbuf_n;	/* dbuf[] size and length */
+static char *rbuf;		/* text in (1 - n_td) direction */
+static int rbuf_sz, rbuf_n;	/* rbuf[] size and length */
+static int dir_cd;		/* current direction */
+
+/* append s to the start (dir == 0) or end (dir == 1) of d */
+static void dir_copy(char **d, int *d_n, int *d_sz, char *s, int s_n, int dir)
+{
+	while (*d_n + s_n + 1 > *d_sz) {
+		int sz = *d_sz ? *d_sz * 2 : 512;
+		char *n = malloc(sz + 1);
+		if (*d_sz)
+			memcpy(dir ? n + *d_sz : n, *d, *d_sz);
+		free(*d);
+		*d_sz = sz;
+		*d = n;
+	}
+	if (dir > 0)
+		memcpy(*d + *d_sz - *d_n - s_n, s, s_n);
+	else
+		memcpy(*d + *d_n, s, s_n);
+	*d_n += s_n;
+}
+
+/* copy rbuf (the text in reverse direction) to dbuf */
+static void dir_flush(void)
+{
+	char *s = rbuf + (n_td > 0 ? 0 : rbuf_sz - rbuf_n);
+	dir_copy(&dbuf, &dbuf_n, &dbuf_sz, s, rbuf_n, n_td);
+	rbuf_n = 0;
+}
+
+/* append s to dbuf or rbuf based on the current text direction */
+static void dir_append(char *s)
+{
+	int dir = dir_cd > 0;
+	if (dir == n_td && rbuf_n)
+		dir_flush();
+	if (dir == n_td)
+		dir_copy(&dbuf, &dbuf_n, &dbuf_sz, s, strlen(s), dir);
+	else
+		dir_copy(&rbuf, &rbuf_n, &rbuf_sz, s, strlen(s), dir);
+}
+
+static void setfont(int f)
+{
+	char cmd[32];
+	sprintf(cmd, "%cf(%02d", c_ec, f);
+	if (f >= 0)
+		dir_append(cmd);
+}
+
+static void setsize(int s)
+{
+	char cmd[32];
+	sprintf(cmd, s <= 99 ? "%cs(%02d" : "%cs[%d]", c_ec, s);
+	if (s >= 0)
+		dir_append(cmd);
+}
+
+static void setcolor(int m)
+{
+	char cmd[32];
+	sprintf(cmd, "%cm[%s]", c_ec, clr_str(m));
+	if (m >= 0)
+		dir_append(cmd);
+}
+
+void dir_fix(struct sbuf *sbuf, char *src)
+{
+	char cmd[ILNLEN];
+	char *prev_s = src;
+	char *r, *c;
+	int f = -1, s = -1, m = -1;
+	int t, n;
+	dir_cd = n_td;
+	while ((t = escread(&src, &c)) >= 0) {
+		cmd[0] = '\0';
+		switch (t) {
+		case 0:
+		case 'D':
+		case 'h':
+		case 'v':
+		case 'x':
+			memcpy(cmd, prev_s, src - prev_s);
+			cmd[src - prev_s] = '\0';
+			dir_append(cmd);
+			break;
+		case 'f':
+			n = atoi(c);
+			if (f != n) {
+				setfont(f);
+				f = n;
+				setfont(f);
+			}
+			break;
+		case 'm':
+			n = clr_get(c);
+			if (m != n) {
+				setcolor(m);
+				m = n;
+				setcolor(m);
+			}
+			break;
+		case 's':
+			n = atoi(c);
+			if (s != n) {
+				setsize(s);
+				s = n;
+				setsize(s);
+			}
+			break;
+		case 'X':
+			sprintf(cmd, "%c%c%s", c_ec, t, c);
+			dir_append(cmd);
+			break;
+		case '<':
+			setcolor(m);
+			setfont(f);
+			setsize(s);
+			dir_cd = 1;
+			setsize(s);
+			setfont(f);
+			setcolor(m);
+			break;
+		case '>':
+			setcolor(m);
+			setfont(f);
+			setsize(s);
+			dir_cd = 0;
+			setsize(s);
+			setfont(f);
+			setcolor(m);
+			break;
+		}
+		prev_s = src;
+	}
+	setcolor(m);
+	setfont(f);
+	setsize(s);
+	dir_flush();
+	r = n_td > 0 ? dbuf + dbuf_sz - dbuf_n : dbuf;
+	r[dbuf_n] = '\0';
+	dbuf_n = 0;
+	sbuf_append(sbuf, r);
+}
+
+void dir_done(void)
+{
+	free(rbuf);
+	free(dbuf);
+}
--- a/fmt.c
+++ b/fmt.c
@@ -16,7 +16,7 @@
 #include <string.h>
 #include "roff.h"
 
-#define FMT_LLEN(f)	MAX(0, (f)->ll - (f)->li)
+#define FMT_LLEN(f)	MAX(0, (f)->ll - (f)->li - (f)->lI)
 #define FMT_FILL(f)	(!n_ce && n_u)
 #define FMT_ADJ(f)	(n_u && !n_na && !n_ce && (n_j & AD_B) == AD_B)
 
@@ -35,7 +35,7 @@
 
 struct line {
 	struct sbuf sbuf;
-	int wid, li, ll;
+	int wid, li, ll, lI;
 	int elsn, elsp;
 };
 
@@ -54,7 +54,7 @@
 	int gap;		/* space before the next word */
 	int nls;		/* newlines before the next word */
 	int nls_sup;		/* suppressed newlines */
-	int li, ll;		/* current line indentation and length */
+	int li, ll, lI;		/* current line indentation and length */
 	int filled;		/* filled all words in the last fmt_fill() */
 	int eos;		/* last word ends a sentence */
 	int fillreq;		/* fill after the last word (\p) */
@@ -65,12 +65,15 @@
 {
 	f->ll = n_l;
 	f->li = n_ti >= 0 ? n_ti : n_i;
+	f->lI = n_tI >= 0 ? n_tI : n_I;
 	n_ti = -1;
+	n_tI = -1;
 }
 
 static int fmt_confchanged(struct fmt *f)
 {
-	return f->ll != n_l || f->li != (n_ti >= 0 ? n_ti : n_i);
+	return f->ll != n_l || f->li != (n_ti >= 0 ? n_ti : n_i) ||
+		f->lI != (n_tI >= 0 ? n_tI : n_I);
 }
 
 /* move words inside an fmt struct */
@@ -144,7 +147,7 @@
 
 /* return the next line in the buffer */
 char *fmt_nextline(struct fmt *f, int *w,
-		int *li, int *ll, int *els_neg, int *els_pos)
+		int *li, int *lI, int *ll, int *els_neg, int *els_pos)
 {
 	struct line *l;
 	if (f->lines_head == f->lines_tail)
@@ -151,6 +154,7 @@
 		return NULL;
 	l = &f->lines[f->lines_tail++];
 	*li = l->li;
+	*lI = l->lI;
 	*ll = l->ll;
 	*w = l->wid;
 	*els_neg = l->elsn;
@@ -172,6 +176,7 @@
 	}
 	l = &f->lines[f->lines_head++];
 	l->li = f->li;
+	l->lI = f->lI;
 	l->ll = f->ll;
 	sbuf_init(&l->sbuf);
 	return l;
@@ -326,7 +331,7 @@
 	char *beg;
 	char *end;
 	int n, i;
-	int cf, cs, cm;
+	int cf, cs, cm, ccd;
 	n = fmt_hyphmarks(src, hyidx, hyins, hygap);
 	if (n <= 0) {
 		fmt_wb2word(f, fmt_mkword(f), wb, 0, 1, gap, wb_cost(wb));
@@ -347,12 +352,12 @@
 		if (i < n && hygap[i])			/* remove \~ */
 			end -= strlen(c_nb);
 		wb_catstr(&wbc, beg, end);
-		wb_fnszget(&wbc, &cf, &cs, &cm);
+		wb_fnszget(&wbc, &cf, &cs, &cm, &ccd);
 		icost = i == n ? wb_cost(&wbc) : hygap[i] * 10000000;
 		igap = i == 0 ? gap : hygap[i - 1] * wb_swid(&wbc);
 		fmt_wb2word(f, fmt_mkword(f), &wbc, ihy, istr, igap, icost);
 		wb_reset(&wbc);
-		wb_fnszset(&wbc, cf, cs, cm);		/* restoring wbc */
+		wb_fnszset(&wbc, cf, cs, cm, ccd);	/* restoring wbc */
 	}
 	wb_done(&wbc);
 }
--- a/reg.c
+++ b/reg.c
@@ -41,6 +41,7 @@
 	".hy", ".hycost", ".hycost2", ".hycost3", ".hlm",
 	".L0", ".m0", ".n0", ".s0", ".ss", ".ssh", ".sss", ".pmll", ".pmllcost",
 	".ti", ".lt", ".lt0", ".v0",
+	".I", ".I0", ".tI", ".td", ".cd",
 };
 
 /* return the address of a number register */
@@ -230,6 +231,7 @@
 		env = envs[id];
 		n_f = 1;
 		n_i = 0;
+		n_I = 0;
 		n_j = AD_B;
 		n_l = SC_IN * 65 / 10;
 		n_L = 1;
@@ -343,7 +345,7 @@
 
 /* saving and restoring registers around diverted lines */
 struct odiv {
-	int f, s, m, f0, s0, m0;
+	int f, s, m, f0, s0, m0, cd;
 };
 
 static struct odiv odivs[NPREV];	/* state before diverted text */
@@ -359,6 +361,7 @@
 	o->f0 = n_f0;
 	o->s0 = n_s0;
 	o->m0 = n_m0;
+	o->cd = n_cd;
 }
 
 /* end outputting diverted line */
@@ -371,6 +374,7 @@
 	n_f0 = o->f0;
 	n_s0 = o->s0;
 	n_m0 = o->m0;
+	n_cd = o->cd;
 }
 
 void tr_ta(char **args)
--- a/ren.c
+++ b/ren.c
@@ -238,10 +238,10 @@
 }
 
 /* line adjustment */
-static int ren_ljust(struct sbuf *spre, int w, int ad, int li, int ll)
+static int ren_ljust(struct sbuf *spre, int w, int ad, int li, int lI, int ll)
 {
 	int ljust = li;
-	int llen = ll - ljust;
+	int llen = ll - lI - li;
 	n_n = w;
 	if ((ad & AD_B) == AD_C)
 		ljust += llen > w ? (llen - w) / 2 : 0;
@@ -271,6 +271,17 @@
 	}
 }
 
+static void ren_dir(struct sbuf *sbuf)
+{
+	struct sbuf fixed;
+	sbuf_init(&fixed);
+	dir_fix(&fixed, sbuf_buf(sbuf));
+	sbuf_done(sbuf);
+	sbuf_init(sbuf);
+	sbuf_append(sbuf, sbuf_buf(&fixed));
+	sbuf_done(&fixed);
+}
+
 static int zwid(void)
 {
 	struct glyph *g = dev_glyph("0", n_f);
@@ -317,13 +328,15 @@
 
 /* process a line and print it with ren_out() */
 static int ren_line(char *line, int w, int ad, int body,
-		int li, int ll, int els_neg, int els_pos)
+		int li, int lI, int ll, int els_neg, int els_pos)
 {
-	struct sbuf sbeg, send;
+	struct sbuf sbeg, send, sbuf;
 	int prev_d, lspc, ljust;
 	ren_first();
 	sbuf_init(&sbeg);
 	sbuf_init(&send);
+	sbuf_init(&sbuf);
+	sbuf_append(&sbuf, line);
 	lspc = MAX(1, n_L) * n_v;	/* line space, ignoreing \x */
 	prev_d = n_d;
 	if (!n_ns || line[0] || els_neg || els_pos) {
@@ -332,10 +345,12 @@
 		ren_sp(0, 0);
 		if (line[0] && n_nm && body)
 			ren_lnum(&sbeg);
-		ljust = ren_ljust(&sbeg, w, ad, li, ll);
+		if (!ren_div && dir_do)
+			ren_dir(&sbuf);
+		ljust = ren_ljust(&sbeg, w, ad, li, lI, ll);
 		if (line[0] && body && n_mc)
 			ren_mc(&send, w, ljust);
-		ren_out(sbuf_buf(&sbeg), line, sbuf_buf(&send));
+		ren_out(sbuf_buf(&sbeg), sbuf_buf(&sbuf), sbuf_buf(&send));
 		n_ns = 0;
 		if (els_pos)
 			ren_sp(els_pos, 1);
@@ -342,6 +357,7 @@
 	}
 	sbuf_done(&sbeg);
 	sbuf_done(&send);
+	sbuf_done(&sbuf);
 	n_a = els_pos;
 	if (detect_traps(prev_d, n_d) || detect_pagelimit(lspc - n_v)) {
 		if (!ren_pagelimit(lspc - n_v))
@@ -357,17 +373,19 @@
 static int ren_passline(struct fmt *fmt)
 {
 	char *buf;
-	int ll, li, els_neg, els_pos, w, ret;
+	int ll, li, lI, els_neg, els_pos, w, ret;
 	int ad = n_j;
 	ren_first();
 	if (!fmt_morewords(fmt))
 		return 0;
-	buf = fmt_nextline(fmt, &w, &li, &ll, &els_neg, &els_pos);
+	buf = fmt_nextline(fmt, &w, &li, &lI, &ll, &els_neg, &els_pos);
 	if ((n_cp && !n_u) || n_na)
 		ad = AD_L;
+	else if ((ad & AD_B) == AD_B)
+		ad = n_td > 0 ? AD_R : AD_L;
 	if (n_ce)
 		ad = AD_C;
-	ret = ren_line(buf, w, ad, 1, li, ll, els_neg, els_pos);
+	ret = ren_line(buf, w, ad, 1, li, lI, ll, els_neg, els_pos);
 	free(buf);
 	return ret;
 }
@@ -546,6 +564,42 @@
 		n_ti = eval_re(args[1], n_i, 'm');
 }
 
+void tr_l2r(char **args)
+{
+	dir_do = 1;
+	if (args[0][0] == c_cc)
+		ren_br();
+	n_td = 0;
+	n_cd = 0;
+}
+
+void tr_r2l(char **args)
+{
+	dir_do = 1;
+	if (args[0][0] == c_cc)
+		ren_br();
+	n_td = 1;
+	n_cd = 1;
+}
+
+void tr_in2(char **args)
+{
+	int I = args[1] ? eval_re(args[1], n_I, 'm') : n_I0;
+	if (args[0][0] == c_cc)
+		ren_br();
+	n_I0 = n_I;
+	n_I = MAX(0, I);
+	n_tI = -1;
+}
+
+void tr_ti2(char **args)
+{
+	if (args[0][0] == c_cc)
+		ren_br();
+	if (args[1])
+		n_tI = eval_re(args[1], n_I, 'm');
+}
+
 static void ren_ft(char *s)
 {
 	int fn = !s || !*s || !strcmp("P", s) ? n_f0 : dev_pos(s);
@@ -710,6 +764,11 @@
 	case ',':
 		wb_italiccorrectionleft(wb);
 		break;
+	case '<':
+	case '>':
+		n_cd = c == '<';
+		wb_flushdir(wb);
+		break;
 	}
 }
 
@@ -739,7 +798,7 @@
 			wb_hmov(wb, w - wb_wid(wb));
 			return;
 		}
-		if (strchr(" bCcDdefHhjkLlmNoprSsuvXxZz0^|!{}&/,", c[1])) {
+		if (strchr(" bCcDdefHhjkLlmNoprSsuvXxZz0^|!{}&/,<>", c[1])) {
 			char *arg = NULL;
 			if (strchr(ESC_P, c[1]))
 				arg = unquotednext(c[1], next, back);
@@ -875,7 +934,7 @@
 	wb_cpy(&wb, &wb2, n_lt - wb_wid(&wb2));
 	/* flushing the line */
 	ren_line(wb_buf(&wb), wb_wid(&wb), AD_L, 0,
-			0, n_lt, wb.els_neg, wb.els_pos);
+			0, 0, n_lt, wb.els_neg, wb.els_pos);
 	wb_done(&wb2);
 	wb_done(&wb);
 }
--- a/roff.c
+++ b/roff.c
@@ -157,5 +157,6 @@
 	env_done();
 	dev_close();
 	map_done();
+	dir_done();
 	return ret;
 }
--- a/roff.h
+++ b/roff.h
@@ -225,8 +225,8 @@
 /* word buffer */
 struct wb {
 	struct sbuf sbuf;
-	int f, s, m;		/* the last output font and size */
-	int r_f, r_s, r_m;	/* current font and size; use n_f and n_s if -1 */
+	int f, s, m, cd;	/* the last output font and size */
+	int r_f, r_s, r_m, r_cd;/* current font and size; use n_f and n_s if -1 */
 	int part;		/* partial input (\c) */
 	int cost;		/* the extra cost of line break after this word */
 	int els_neg, els_pos;	/* extra line spacing */
@@ -274,8 +274,9 @@
 		int *llx, int *lly, int *urx, int *ury);
 void wb_reset(struct wb *wb);
 char *wb_buf(struct wb *wb);
-void wb_fnszget(struct wb *wb, int *fn, int *sz, int *m);
-void wb_fnszset(struct wb *wb, int fn, int sz, int m);
+void wb_fnszget(struct wb *wb, int *fn, int *sz, int *m, int *cd);
+void wb_fnszset(struct wb *wb, int fn, int sz, int m, int cd);
+void wb_flushdir(struct wb *wb);
 int wb_hywid(struct wb *wb);
 int wb_swid(struct wb *wb);
 int c_eossent(char *s);
@@ -321,7 +322,7 @@
 int fmt_morelines(struct fmt *fmt);
 int fmt_morewords(struct fmt *fmt);
 char *fmt_nextline(struct fmt *fmt, int *w,
-		int *li, int *ll, int *els_neg, int *els_pos);
+		int *li, int *lI, int *ll, int *els_neg, int *els_pos);
 
 /* rendering */
 int render(void);				/* the main loop */
@@ -383,6 +384,11 @@
 void tr_popren(char **args);
 void tr_transparent(char **args);
 
+void tr_in2(char **args);
+void tr_ti2(char **args);
+void tr_l2r(char **args);
+void tr_r2l(char **args);
+
 void tr_init(void);
 void tr_done(void);
 
@@ -426,6 +432,12 @@
 char *map_name(int id);		/* return the name mapped to id */
 void map_done(void);
 
+/* text direction */
+extern int dir_do;
+
+void dir_fix(struct sbuf *sbuf, char *s);
+void dir_done(void);
+
 /* colors */
 #define CLR_R(c)		(((c) >> 16) & 0xff)
 #define CLR_G(c)		(((c) >> 8) & 0xff)
@@ -444,6 +456,7 @@
 #define n_i		(*nreg(DOTMAP('i')))
 #define n_it		(*nreg(map(".it")))	/* .it trap macro */
 #define n_itn		(*nreg(map(".itn")))	/* .it lines left */
+#define n_I		(*nreg(DOTMAP('I')))	/* base indent */
 #define n_j		(*nreg(DOTMAP('j')))
 #define n_l		(*nreg(DOTMAP('l')))
 #define n_L		(*nreg(DOTMAP('L')))
@@ -462,6 +475,8 @@
 #define n_u		(*nreg(DOTMAP('u')))
 #define n_v		(*nreg(DOTMAP('v')))
 #define n_ct		(*nreg(map("ct")))
+#define n_td		(*nreg(map(".td")))	/* text direction */
+#define n_cd		(*nreg(map(".cd")))	/* current direction */
 #define n_dl		(*nreg(map("dl")))
 #define n_dn		(*nreg(map("dn")))
 #define n_ln		(*nreg(map("ln")))
@@ -481,6 +496,8 @@
 #define n_i0		(*nreg(map(".i0")))	/* last .i */
 #define n_ti		(*nreg(map(".ti")))	/* pending .ti */
 #define n_kn		(*nreg(map(".kn")))	/* .kn mode */
+#define n_tI		(*nreg(map(".tI")))	/* pending .ti2 */
+#define n_I0		(*nreg(map(".I0")))	/* last .I */
 #define n_l0		(*nreg(map(".l0")))	/* last .l */
 #define n_L0		(*nreg(map(".L0")))	/* last .L */
 #define n_m0		(*nreg(map(".m0")))	/* last .m */
--- a/tr.c
+++ b/tr.c
@@ -1020,6 +1020,8 @@
 	{TR_DIVEND, tr_divend},
 	{TR_DIVVS, tr_divvs},
 	{TR_POPREN, tr_popren},
+	{">>", tr_l2r},
+	{"<<", tr_r2l},
 	{"ab", tr_ab, mkargs_eol},
 	{"ad", tr_ad},
 	{"af", tr_af},
@@ -1075,6 +1077,7 @@
 	{"if", tr_if, mkargs_null},
 	{"ig", tr_ig},
 	{"in", tr_in},
+	{"in2", tr_in2},
 	{"it", tr_it},
 	{"kn", tr_kn},
 	{"lc", tr_lc},
@@ -1116,6 +1119,7 @@
 	{"ta", tr_ta},
 	{"tc", tr_tc},
 	{"ti", tr_ti},
+	{"ti2", tr_ti2},
 	{"tkf", tr_tkf},
 	{"tl", tr_tl, mkargs_null},
 	{"tm", tr_tm, mkargs_eol},
--- a/wb.c
+++ b/wb.c
@@ -9,6 +9,7 @@
 #define R_F(wb)		((wb)->r_f >= 0 ? (wb)->r_f : n_f)	/* current font */
 #define R_S(wb)		((wb)->r_s >= 0 ? (wb)->r_s : n_s)	/* current size */
 #define R_M(wb)		((wb)->r_m >= 0 ? (wb)->r_m : n_m)	/* current color */
+#define R_CD(b)		((wb)->r_cd >= 0 ? (wb)->r_cd : n_cd)	/* current direction */
 /* italic correction */
 #define glyph_ic(g)	(MAX(0, (g)->urx - (g)->wid))
 #define glyph_icleft(g)	(MAX(0, -(g)->llx))
@@ -26,9 +27,11 @@
 	wb->f = -1;
 	wb->s = -1;
 	wb->m = -1;
+	wb->cd = -1;
 	wb->r_f = -1;
 	wb->r_s = -1;
 	wb->r_m = -1;
+	wb->r_cd = -1;
 	wb->llx = BBMAX;
 	wb->lly = BBMAX;
 	wb->urx = BBMIN;
@@ -63,6 +66,12 @@
 			(!n_cp && wb->m != R_M(wb));
 }
 
+/* pending direction change */
+static int wb_pendingdir(struct wb *wb)
+{
+	return wb->cd != R_CD(wb);
+}
+
 /* append font and size to the buffer if needed */
 static void wb_flushfont(struct wb *wb)
 {
@@ -84,10 +93,22 @@
 	wb_stsb(wb);
 }
 
+/* append current text direction to the buffer if needed */
+void wb_flushdir(struct wb *wb)
+{
+	if (wb->cd != R_CD(wb)) {
+		wb_flushsub(wb);
+		if (dir_do)
+			sbuf_printf(&wb->sbuf, "%c%c", c_ec, R_CD(wb) > 0 ? '<' : '>');
+		wb->cd = R_CD(wb);
+	}
+}
+
 /* apply font and size changes and flush the collected subword */
 static void wb_flush(struct wb *wb)
 {
 	wb_flushsub(wb);
+	wb_flushdir(wb);
 	wb_flushfont(wb);
 }
 
@@ -220,10 +241,12 @@
 		dst_n = font_layout(fn, gsrc, sidx - beg, wb->s,
 				gdst, dmap, x, y, xadv, yadv, n_lg, n_kn);
 		for (i = 0; i < dst_n; i++) {
-			if (x[i])
-				wb_hmov(wb, font_wid(fn, wb->s, x[i]));
-			if (y[i])
-				wb_vmov(wb, font_wid(fn, wb->s, y[i]));
+			int xd[2] = {x[i], xadv[i] - x[i]};
+			int yd[2] = {y[i], yadv[i] - y[i]};
+			if (xd[wb->cd])
+				wb_hmov(wb, font_wid(fn, wb->s, xd[wb->cd]));
+			if (yd[wb->cd])
+				wb_vmov(wb, font_wid(fn, wb->s, yd[wb->cd]));
 			if (src_hyph[beg + dmap[i]])
 				wb_putbuf(wb, c_hc);
 			if (gdst[i] == gsrc[dmap[i]])
@@ -230,10 +253,10 @@
 				wb_putbuf(wb, wb->sub_c[beg + dmap[i]]);
 			else
 				wb_putbuf(wb, gdst[i]->name);
-			if (x[i] || xadv[i])
-				wb_hmov(wb, font_wid(fn, wb->s, xadv[i] - x[i]));
-			if (y[i] || yadv[i])
-				wb_vmov(wb, font_wid(fn, wb->s, yadv[i] - y[i]));
+			if (xd[1 - wb->cd])
+				wb_hmov(wb, font_wid(fn, wb->s, xd[1 - wb->cd]));
+			if (yd[1 - wb->cd])
+				wb_vmov(wb, font_wid(fn, wb->s, yd[1 - wb->cd]));
 		}
 		for (; sidx < wb->sub_n && c_hymark(wb->sub_c[sidx]); sidx++)
 			wb_putbuf(wb, wb->sub_c[sidx]);
@@ -249,6 +272,8 @@
 		wb->part = 0;
 		return;
 	}
+	if (wb_pendingdir(wb))
+		wb_flushdir(wb);
 	if (c[0] == ' ') {
 		wb_flushsub(wb);
 		wb_hmov(wb, font_swid(dev_font(R_F(wb)), R_S(wb), n_ss));
@@ -411,6 +436,11 @@
 	case 'X':
 		wb_etc(wb, s);
 		break;
+	case '<':
+	case '>':
+		wb->r_cd = t == '<';
+		wb_flushdir(wb);
+		break;
 	}
 }
 
@@ -429,6 +459,7 @@
 	wb->r_s = -1;
 	wb->r_f = -1;
 	wb->r_m = -1;
+	wb->r_cd = -1;
 	wb_reset(src);
 	src->part = part;
 	wb_collect(wb, collect);
@@ -497,19 +528,21 @@
 	wb->icleft = 1;
 }
 
-void wb_fnszget(struct wb *wb, int *fn, int *sz, int *m)
+void wb_fnszget(struct wb *wb, int *fn, int *sz, int *m, int *cd)
 {
 	wb_flushsub(wb);
 	*fn = wb->r_f;
 	*sz = wb->r_s;
 	*m = wb->r_m;
+	*cd = wb->r_cd;
 }
 
-void wb_fnszset(struct wb *wb, int fn, int sz, int m)
+void wb_fnszset(struct wb *wb, int fn, int sz, int m, int cd)
 {
 	wb->r_f = fn;
 	wb->r_s = sz;
 	wb->r_m = m;
+	wb->r_cd = cd;
 }
 
 void wb_catstr(struct wb *wb, char *s, char *end)