Commit Diff


commit - 49a1496cbbb871bc623cfd0925566628e246c9ba
commit + c42a1d3d6168df56f966ea1f3ba3ef39ebbff4e4
blob - /dev/null
blob + c17da850c6552993a6429feada4870782574a4fd (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/a.h
@@ -0,0 +1,148 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+
+enum
+{
+	Unbsp = 0x00A0,
+	Uprivate = 0xF000,
+	Uempty,	/* \& */
+	Uamp,	/* raw & */
+	Ult,		/* raw < */
+	Ugt,		/* raw > */
+	Utick,	/* raw ' */
+	Ubtick,	/* raw ` */
+	Uminus,	/* raw - */
+	Uspace,	/* raw space */
+	Upl,		/* symbol + */
+	Ueq,		/* symbol = */
+	Umi,		/* symbol - */
+	Uformatted,	/* start diverted output */
+	Uunformatted,	/* end diverted output */
+
+	UPI = 720,	/* units per inch */
+	UPX = 10,	/* units per pixel */
+	
+	/* special input modes */
+	CopyMode = 1<<1,
+	ExpandMode = 1<<2,
+	ArgMode = 1<<3,
+	HtmlMode = 1<<4,
+	
+	MaxLine = 1024,
+};
+
+Rune*	L(char*);
+
+void		addesc(Rune, int (*)(void), int);
+void		addraw(Rune*, void(*)(Rune*));
+void		addreq(Rune*, void(*)(int, Rune**), int);
+void		af(Rune*, Rune*);
+void		as(Rune*, Rune*);
+void		br(void);
+void		closehtml(void);
+Rune*	copyarg(void);
+void		delraw(Rune*);
+void		delreq(Rune*);
+void		ds(Rune*, Rune*);
+int		dv(int);
+int		e_nop(void);
+int		e_warn(void);
+void*	emalloc(uint);
+void*	erealloc(void*, uint);
+Rune*	erunesmprint(char*, ...);
+Rune*	erunestrdup(Rune*);
+char*	esmprint(char*, ...);
+char*	estrdup(char*);
+int		eval(Rune*);
+int		evalscale(Rune*, int);
+Rune*	getname(void);
+int		getnext(void);
+Rune*	getds(Rune*);
+Rune*	_getnr(Rune*);
+int		getnr(Rune*);
+int		getnrr(Rune*);
+int		getrune(void);
+Rune*	getqarg(void);
+Rune*	getline(void);
+void		hideihtml(void);
+void		html(Rune*, Rune*);
+void		htmlinit(void);
+void		ihtml(Rune*, Rune*);
+void		inputnotify(void(*)(void));
+void		itrap(void);
+void		itrapset(void);
+int		linefmt(Fmt*);
+void		nr(Rune*, int);
+void		_nr(Rune*, Rune*);
+void		out(Rune*);
+void		(*outcb)(Rune);
+void		outhtml(Rune*);
+void		outrune(Rune);
+void		outtrap(void);
+int		popinput(void);
+void		printds(int);
+int		pushinputfile(Rune*);
+void		pushinputstring(Rune*);
+int		pushstdin(void);
+int		queueinputfile(Rune*);
+int		queuestdin(void);
+void		r_nop(int, Rune**);
+void		r_warn(int, Rune**);
+Rune	*readline(int);
+void		reitag(void);
+void		renraw(Rune*, Rune*);
+void		renreq(Rune*, Rune*);
+void		run(void);
+void		runinput(void);
+int		runmacro(int, int, Rune**);
+void		runmacro1(Rune*);
+Rune*	rune2html(Rune);
+void		setlinenumber(Rune*, int);
+void		showihtml(void);
+void		sp(int);
+void		t1init(void);
+void		t2init(void);
+void		t3init(void);
+void		t4init(void);
+void		t5init(void);
+void		t6init(void);
+void		t7init(void);
+void		t8init(void);
+void		t9init(void);
+void		t10init(void);
+void		t11init(void);
+void		t12init(void);
+void		t13init(void);
+void		t14init(void);
+void		t15init(void);
+void		t16init(void);
+void		t17init(void);
+void		t18init(void);
+void		t19init(void);
+void		t20init(void);
+Rune	troff2rune(Rune*);
+void		unfont(void);
+void		ungetnext(Rune);
+void		ungetrune(Rune);
+void		unitag(void);
+void		warn(char*, ...);
+
+extern	int		backslash;
+extern	int		bol;
+extern	Biobuf	bout;
+extern	int		broke;
+extern	int		dot;
+extern	int		inputmode;
+extern	int		inrequest;
+extern	int		tick;
+extern	int		utf8;
+extern	int		verbose;
+extern	int		linepos;
+
+#define	runemalloc(n)	(Rune*)emalloc((n)*sizeof(Rune))
+#define	runerealloc(r, n)	(Rune*)erealloc(r, (n)*sizeof(Rune))
+#define	runemove(a, b, n)	memmove(a, b, (n)*sizeof(Rune))
+
+#pragma varargck type "L" void
blob - /dev/null
blob + 1c7d1237b4145775170a8716ad6b21866ad0b57a (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/char.c
@@ -0,0 +1,116 @@
+#include "a.h"
+
+/*
+ * Translate Unicode to HTML by asking tcs(1).
+ * This way we don't have yet another table.
+ */
+Rune*
+rune2html(Rune r)
+{
+	static Biobuf b;
+	static int fd = -1;
+	static Rune **tcscache[256];
+	int p[2];
+	char *q;
+	
+	if(r == '\n')
+		return L("\n");
+
+	if(tcscache[r>>8] && tcscache[r>>8][r&0xFF])
+		return tcscache[r>>8][r&0xFF];
+
+	if(fd < 0){
+		if(pipe(p) < 0)
+			sysfatal("pipe: %r");
+		switch(fork()){
+		case -1:
+			sysfatal("fork: %r");
+		case 0:
+			dup(p[0], 0);
+			dup(p[0], 1);
+			close(p[1]);
+			execl("tcs", "tcs", "-t", "html", nil);
+			_exits(0);
+		default:
+			close(p[0]);
+			fd = p[1];
+			Binit(&b, fd, OREAD);
+			break;
+		}
+	}
+	fprint(fd, "%C\n", r);
+	q = Brdline(&b, '\n');
+	if(q == nil)
+		sysfatal("tcs: early eof");
+	q[Blinelen(&b)-1] = 0;
+	if(tcscache[r>>8] == nil)
+		tcscache[r>>8] = emalloc(256*sizeof tcscache[0][0]);
+	tcscache[r>>8][r&0xFF] = erunesmprint("%s", q);
+	return tcscache[r>>8][r&0xFF];
+}
+
+/*
+ * Translate troff to Unicode by looking in troff's utfmap.
+ * This way we don't have yet another hard-coded table.
+ */
+typedef struct Trtab Trtab;
+struct Trtab
+{
+	char t[3];
+	Rune r;
+};
+
+static Trtab trtab[200];
+int ntrtab;
+
+static Trtab trinit[] =
+{
+	"pl",		Upl,
+	"eq",	Ueq,
+	"em",	0x2014,
+	"en",	0x2013,
+	"mi",	Umi,
+	"fm",	0x2032,
+};
+
+Rune
+troff2rune(Rune *rs)
+{
+	char *file, *f[10], *p, s[3];
+	int i, nf;
+	Biobuf *b;
+	
+	if(rs[0] >= Runeself || rs[1] >= Runeself)
+		return Runeerror;
+	s[0] = rs[0];
+	s[1] = rs[1];
+	s[2] = 0;
+	if(ntrtab == 0){
+		for(i=0; i<nelem(trinit) && ntrtab < nelem(trtab); i++){
+			trtab[ntrtab] = trinit[i];
+			ntrtab++;
+		}
+		file = "/sys/lib/troff/font/devutf/utfmap";
+		if((b = Bopen(file, OREAD)) == nil)
+			sysfatal("open %s: %r", file);
+		while((p = Brdline(b, '\n')) != nil){
+			p[Blinelen(b)-1] = 0;
+			nf = getfields(p, f, nelem(f), 0, "\t");
+			for(i=0; i+2<=nf && ntrtab<nelem(trtab); i+=2){
+				chartorune(&trtab[ntrtab].r, f[i]);
+				memmove(trtab[ntrtab].t, f[i+1], 2);
+				ntrtab++;
+			}
+		}
+		Bterm(b);
+		
+		if(ntrtab >= nelem(trtab))
+			fprint(2, "%s: trtab too small\n", argv0);
+	}
+	
+	for(i=0; i<ntrtab; i++)
+		if(strcmp(s, trtab[i].t) == 0)
+			return trtab[i].r;
+	return Runeerror;
+}
+
blob - /dev/null
blob + fd48382ad105157f4cc0cc1c4e6f24746bc301c4 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/html.c
@@ -0,0 +1,287 @@
+/*
+ * Emit html.  Keep track of tags so that user doesn't have to.
+ */
+
+#include "a.h"
+
+typedef struct Tag Tag;
+struct Tag
+{
+	Tag *next;
+	Rune *id;
+	Rune *open;
+	Rune *close;
+};
+
+Tag *tagstack;
+Tag *tagset;
+int hidingset;
+
+static Rune*
+closingtag(Rune *s)
+{
+	Rune *t;
+	Rune *p0, *p;
+	
+	t = runemalloc(sizeof(Rune));
+	if(s == nil)
+		return t;
+	for(p=s; *p; p++){
+		if(*p == Ult){
+			p++;
+			if(*p == '/'){
+				while(*p && *p != Ugt)
+					p++;
+				goto close;
+			}
+			p0 = p;
+			while(*p && !isspacerune(*p) && *p != Uspace && *p != Ugt)
+				p++;
+			t = runerealloc(t, 1+(p-p0)+2+runestrlen(t)+1);
+			runemove(t+(p-p0)+3, t, runestrlen(t)+1);
+			t[0] = Ult;
+			t[1] = '/';
+			runemove(t+2, p0, p-p0);
+			t[2+(p-p0)] = Ugt;
+		}
+		
+		if(*p == Ugt && p>s && *(p-1) == '/'){
+		close:
+			for(p0=t+1; *p0 && *p0 != Ult; p0++)
+				;
+			runemove(t, p0, runestrlen(p0)+1);
+		}
+	}
+	return t;	
+}
+
+void
+html(Rune *id, Rune *s)
+{
+	Rune *es;
+	Tag *t, *tt, *next;
+
+	br();
+	hideihtml();	/* br already did, but be paranoid */
+	for(t=tagstack; t; t=t->next){
+		if(runestrcmp(t->id, id) == 0){
+			for(tt=tagstack;; tt=next){
+				next = tt->next;
+				free(tt->id);
+				free(tt->open);
+				out(tt->close);
+				outrune('\n');
+				free(tt->close);
+				free(tt);
+				if(tt == t){
+					tagstack = next;
+					goto cleared;
+				}
+			}
+		}
+	}
+
+cleared:
+	if(s == nil || s[0] == 0)
+		return;
+	out(s);
+	outrune('\n');
+	es = closingtag(s);
+	if(es[0] == 0){
+		free(es);
+		return;
+	}
+	if(runestrcmp(id, L("-")) == 0){
+		out(es);
+		outrune('\n');
+		free(es);
+		return;
+	}
+	t = emalloc(sizeof *t);
+	t->id = erunestrdup(id);
+	t->close = es;
+	t->next = tagstack;
+	tagstack = t;
+}
+
+void
+closehtml(void)
+{
+	Tag *t, *next;
+	
+	br();
+	hideihtml();
+	for(t=tagstack; t; t=next){
+		next = t->next;
+		out(t->close);
+		outrune('\n');
+		free(t->id);
+		free(t->close);
+		free(t);
+	}
+}
+
+static void
+rshow(Tag *t, Tag *end)
+{
+	if(t == nil || t == end)
+		return;
+	rshow(t->next, end);
+	out(t->open);
+}
+
+void
+ihtml(Rune *id, Rune *s)
+{
+	Tag *t, *tt, **l;
+
+	for(t=tagset; t; t=t->next){
+		if(runestrcmp(t->id, id) == 0){
+			if(s && t->open && runestrcmp(t->open, s) == 0)
+				return;
+			for(l=&tagset; (tt=*l); l=&tt->next){
+				if(!hidingset)
+					out(tt->close);
+				if(tt == t)
+					break;
+			}
+			*l = t->next;
+			free(t->id);
+			free(t->close);
+			free(t->open);
+			free(t);
+			if(!hidingset)
+				rshow(tagset, *l);
+			goto cleared;
+		}
+	}
+
+cleared:
+	if(s == nil || s[0] == 0)
+		return;
+	t = emalloc(sizeof *t);
+	t->id = erunestrdup(id);
+	t->open = erunestrdup(s);
+	t->close = closingtag(s);
+	if(!hidingset)
+		out(s);
+	t->next = tagset;
+	tagset = t;
+}
+
+void
+hideihtml(void)
+{
+	Tag *t;
+
+	if(hidingset)
+		return;
+	hidingset = 1;
+	for(t=tagset; t; t=t->next)
+		out(t->close);
+}
+
+void
+showihtml(void)
+{
+	if(!hidingset)
+		return;
+	hidingset = 0;
+	rshow(tagset, nil);
+}
+
+int
+e_lt(void)
+{
+	return Ult;
+}
+
+int
+e_gt(void)
+{
+	return Ugt;
+}
+
+int
+e_at(void)
+{
+	return Uamp;
+}
+
+int
+e_tick(void)
+{
+	return Utick;
+}
+
+int
+e_btick(void)
+{
+	return Ubtick;
+}
+
+int
+e_minus(void)
+{
+	return Uminus;
+}
+
+void
+r_html(Rune *name)
+{
+	Rune *id, *line, *p;
+	
+	id = copyarg();
+	line = readline(HtmlMode);
+	for(p=line; *p; p++){
+		switch(*p){
+		case '<':
+			*p = Ult;
+			break;
+		case '>':
+			*p = Ugt;
+			break;
+		case '&':
+			*p = Uamp;
+			break;
+		case ' ':
+			*p = Uspace;
+			break;
+		}
+	}
+	if(name[0] == 'i')
+		ihtml(id, line);
+	else
+		html(id, line);
+	free(id);
+	free(line);
+}
+
+char defaultfont[] =
+	".ihtml f1\n"
+	".ihtml f\n"
+	".ihtml f <span style=\"font-size=\\n(.spt\">\n"
+	".if \\n(.f==2 .ihtml f1 <i>\n"
+	".if \\n(.f==3 .ihtml f1 <b>\n"
+	".if \\n(.f==4 .ihtml f1 <b><i>\n"
+	".if \\n(.f==5 .ihtml f1 <tt>\n"
+	".if \\n(.f==6 .ihtml f1 <tt><i>\n"
+	"..\n"
+;
+
+void
+htmlinit(void)
+{
+	addraw(L("html"), r_html);
+	addraw(L("ihtml"), r_html);
+
+	addesc('<', e_lt, CopyMode);
+	addesc('>', e_gt, CopyMode);
+	addesc('\'', e_tick, CopyMode);
+	addesc('`', e_btick, CopyMode);
+	addesc('-', e_minus, CopyMode);
+	addesc('@', e_at, CopyMode);
+	
+	ds(L("font"), L(defaultfont));
+}
+
blob - /dev/null
blob + 99e0d56e9d806cc79794bfee92724380b94c0b3a (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/input.c
@@ -0,0 +1,241 @@
+/*
+ * Read input files.
+ */
+#include "a.h"
+
+typedef struct Istack Istack;
+struct Istack
+{
+	Rune unget[3];
+	int nunget;
+	Biobuf *b;
+	Rune *p;
+	Rune *ep;
+	Rune *s;
+	int lineno;
+	Rune *name;
+	Istack *next;
+	void (*fn)(void);
+};
+
+Istack *istack;
+Istack *ibottom;
+
+static void
+setname(void)
+{
+	Rune *r, *p;
+
+	if(istack == nil || istack->name == nil)
+		return;
+	_nr(L(".F"), istack->name);
+	r = erunestrdup(istack->name);
+	p = runestrchr(r, '.');
+	if(p)
+		*p = 0;
+	_nr(L(".B"), r);
+	free(r);
+}
+
+static void
+ipush(Istack *is)
+{
+	if(istack == nil)
+		ibottom = is;
+	else
+		is->next = istack;
+	istack = is;
+	setname();
+}
+
+static void
+iqueue(Istack *is)
+{
+	if(ibottom == nil){
+		istack = is;
+		setname();
+	}else
+		ibottom->next = is;
+	ibottom = is;
+}
+
+int
+_inputfile(Rune *s, void (*push)(Istack*))
+{
+	Istack *is;
+	Biobuf *b;
+	char *t;
+	
+	t = esmprint("%S", s);
+	if((b = Bopen(t, OREAD)) == nil){
+		free(t);
+		fprint(2, "%s: open %S: %r\n", argv0, s);
+		return -1;
+	}
+	free(t);
+	is = emalloc(sizeof *is);
+	is->b = b;
+	is->name = erunestrdup(s);
+	is->lineno = 1;
+	push(is);
+	return 0;
+}
+
+int
+pushinputfile(Rune *s)
+{
+	return _inputfile(s, ipush);
+}
+
+int
+queueinputfile(Rune *s)
+{
+	return _inputfile(s, iqueue);
+}
+
+int
+_inputstdin(void (*push)(Istack*))
+{	
+	Biobuf *b;
+	Istack *is;
+
+	if((b = Bopen("/dev/null", OREAD)) == nil){
+		fprint(2, "%s: open /dev/null: %r\n", argv0);
+		return -1;
+	}
+	dup(0, b->fid);
+	is = emalloc(sizeof *is);
+	is->b = b;
+	is->name = erunestrdup(L("stdin"));
+	is->lineno = 1;
+	push(is);
+	return 0;
+}
+
+int
+pushstdin(void)
+{
+	return _inputstdin(ipush);
+}
+
+int
+queuestdin(void)
+{
+	return _inputstdin(iqueue);
+}
+
+void
+_inputstring(Rune *s, void (*push)(Istack*))
+{
+	Istack *is;
+	
+	is = emalloc(sizeof *is);
+	is->s = erunestrdup(s);
+	is->p = is->s;
+	is->ep = is->p+runestrlen(is->p);
+	push(is);
+}
+
+void
+pushinputstring(Rune *s)
+{
+	_inputstring(s, ipush);
+}
+
+
+void
+inputnotify(void (*fn)(void))
+{
+	if(istack)
+		istack->fn = fn;
+}
+
+int
+popinput(void)
+{
+	Istack *is;
+
+	is = istack;
+	if(is == nil)
+		return 0;
+
+	istack = istack->next;
+	if(is->b)
+		Bterm(is->b);
+	free(is->s);
+	free(is->name);
+	if(is->fn)
+		is->fn();
+	free(is);
+	setname();
+	return 1;
+}
+
+int
+getrune(void)
+{
+	Rune r;
+	int c;
+	
+top:
+	if(istack == nil)
+		return -1;
+	if(istack->nunget)
+		return istack->unget[--istack->nunget];
+	else if(istack->p){
+		if(istack->p >= istack->ep){
+			popinput();
+			goto top;
+		}
+		r = *istack->p++;
+	}else if(istack->b){
+		if((c = Bgetrune(istack->b)) < 0){
+			popinput();
+			goto top;
+		}
+		r = c;
+	}else{
+		r = 0;
+		sysfatal("getrune - can't happen");
+	}
+	if(r == '\n')
+		istack->lineno++;	
+	return r;
+}
+
+void
+ungetrune(Rune r)
+{
+	if(istack == nil || istack->nunget >= nelem(istack->unget))
+		pushinputstring(L(""));
+	istack->unget[istack->nunget++] = r;
+}
+
+int
+linefmt(Fmt *f)
+{
+	Istack *is;
+	
+	for(is=istack; is && !is->b; is=is->next)
+		;
+	if(is)
+		return fmtprint(f, "%S:%d", is->name, is->lineno);
+	else
+		return fmtprint(f, "<no input>");
+}
+
+void
+setlinenumber(Rune *s, int n)
+{
+	Istack *is;
+	
+	for(is=istack; is && !is->name; is=is->next)
+		;
+	if(is){
+		if(s){
+			free(is->name);
+			is->name = erunestrdup(s);
+		}
+		is->lineno = n;
+	}
+}
blob - /dev/null
blob + b6af1e7b405495ec02c6b434d5aa37df656794c9 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/main.c
@@ -0,0 +1,72 @@
+/*
+ * Convert troff -ms input to HTML.
+ */
+
+#include "a.h"
+
+Biobuf	bout;
+char*	tmacdir;
+int		verbose;
+int		utf8 = 0;
+
+void
+usage(void)
+{
+	fprint(2, "usage: htmlroff [-iuv] [-m mac] [-r an] [file...]\n");
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int i, dostdin;
+	char *p;
+	Rune *r;
+	Rune buf[2];
+	
+	Binit(&bout, 1, OWRITE);
+	fmtinstall('L', linefmt);
+	quotefmtinstall();
+	
+	tmacdir = unsharp("#9/tmac");
+	dostdin = 0;
+	ARGBEGIN{
+	case 'i':
+		dostdin = 1;
+		break;
+	case 'm':
+		r = erunesmprint("%s/tmac.%s", tmacdir, EARGF(usage()));
+		if(queueinputfile(r) < 0)
+			fprint(2, "%S: %r\n", r);
+		break;
+	case 'r':
+		p = EARGF(usage());
+		p += chartorune(buf, p);
+		buf[1] = 0;
+		_nr(buf, erunesmprint("%s", p+1));
+		break;
+	case 'u':
+		utf8 = 1;
+		break;
+	case 'v':
+		verbose = 1;
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	for(i=0; i<argc; i++){
+		if(strcmp(argv[i], "-") == 0)
+			queuestdin();
+		else
+			queueinputfile(erunesmprint("%s", argv[i]));
+	}
+	if(argc == 0 || dostdin)
+		queuestdin();
+	
+	run();
+	Bprint(&bout, "\n");
+	Bterm(&bout);
+	exits(nil);
+}
+
blob - /dev/null
blob + 641bf803a36c58036f4e44a4ba118c15ba7c0420 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/mkfile
@@ -0,0 +1,58 @@
+<$PLAN9/src/mkhdr
+
+TARG=htmlroff
+
+OFILES=\
+	char.$O\
+	html.$O\
+	input.$O\
+	main.$O\
+	roff.$O\
+	t1.$O\
+	t2.$O\
+	t3.$O\
+	t4.$O\
+	t5.$O\
+	t6.$O\
+	t7.$O\
+	t8.$O\
+#	t9.$O\
+	t10.$O\
+	t11.$O\
+#	t12.$O\
+	t13.$O\
+	t14.$O\
+	t15.$O\
+	t16.$O\
+	t17.$O\
+	t18.$O\
+	t19.$O\
+	t20.$O\
+	util.$O\
+
+HFILES=a.h
+
+<$PLAN9/src/mkone
+
+auth:V: auth.html
+	web auth.html
+
+auth.html: o.htmlroff auth.ms htmlmac.s 
+	9 pic auth.ms | 9 eqn | ./o.htmlroff -ms >auth.html
+	# 9 pic auth.ms | 9 eqn | ./o.htmlroff htmlmac.s /usr/local/plan9/tmac/tmac.skeep - >auth.html
+
+test%.html: o.htmlroff test.% htmlmac.s
+	./o.htmlroff htmlmac.s test.$stem - >$target
+
+eqn:V: eqn.html
+	web eqn.html
+
+eqn.html: o.htmlroff htmlmac.s eqn.ms
+	9 eqn eqn.ms | ./o.htmlroff htmlmac.s - >eqn.html
+
+eqn0.html: o.htmlroff htmlmac.s eqn0.ms
+	./o.htmlroff htmlmac.s eqn0.ms - >eqn0.html
+
+rc.html: o.htmlroff rc.ms htmlmac.s
+	9 tbl rc.ms | ./o.htmlroff -ms >rc.html
+
blob - /dev/null
blob + 6a7cd09e4c64b81d7079e2c8cb738177233c638e (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/roff.c
@@ -0,0 +1,750 @@
+#include "a.h"
+
+enum
+{
+	MAXREQ = 100,
+	MAXRAW = 40,
+	MAXESC = 60,
+	MAXLINE = 1024,
+	MAXIF = 20,
+	MAXARG = 10,
+};
+
+typedef struct Esc Esc;
+typedef struct Req Req;
+typedef struct Raw Raw;
+
+/* escape sequence handler, like for \c */
+struct Esc
+{
+	Rune r;
+	int (*f)(void);
+	int mode;
+};
+
+/* raw request handler, like for .ie */
+struct Raw
+{
+	Rune *name;
+	void (*f)(Rune*);
+};
+
+/* regular request handler, like for .ft */
+struct Req
+{
+	int argc;
+	Rune *name;
+	void (*f)(int, Rune**);
+};
+
+int		dot = '.';
+int		tick = '\'';
+int		backslash = '\\';
+
+int		inputmode;
+Req		req[MAXREQ];
+int		nreq;
+Raw		raw[MAXRAW];
+int		nraw;
+Esc		esc[MAXESC];
+int		nesc;
+int		iftrue[MAXIF];
+int		niftrue;
+
+int isoutput;
+int linepos;
+
+
+void
+addraw(Rune *name, void (*f)(Rune*))
+{
+	Raw *r;
+	
+	if(nraw >= nelem(raw)){
+		fprint(2, "too many raw requets\n");
+		return;
+	}
+	r = &raw[nraw++];
+	r->name = erunestrdup(name);
+	r->f = f;
+}
+
+void
+delraw(Rune *name)
+{
+	int i;
+	
+	for(i=0; i<nraw; i++){
+		if(runestrcmp(raw[i].name, name) == 0){
+			if(i != --nraw){
+				free(raw[i].name);
+				raw[i] = raw[nraw];
+			}
+			return;
+		}
+	}
+}
+
+void
+renraw(Rune *from, Rune *to)
+{
+	int i;
+	
+	delraw(to);
+	for(i=0; i<nraw; i++)
+		if(runestrcmp(raw[i].name, from) == 0){
+			free(raw[i].name);
+			raw[i].name = erunestrdup(to);
+			return;
+		}
+}
+
+
+void
+addreq(Rune *s, void (*f)(int, Rune**), int argc)
+{
+	Req *r;
+
+	if(nreq >= nelem(req)){
+		fprint(2, "too many requests\n");
+		return;
+	}
+	r = &req[nreq++];
+	r->name = erunestrdup(s);
+	r->f = f;
+	r->argc = argc;
+}
+
+void
+delreq(Rune *name)
+{
+	int i;
+
+	for(i=0; i<nreq; i++){
+		if(runestrcmp(req[i].name, name) == 0){
+			if(i != --nreq){
+				free(req[i].name);
+				req[i] = req[nreq];
+			}
+			return;
+		}
+	}
+}
+
+void
+renreq(Rune *from, Rune *to)
+{
+	int i;
+	
+	delreq(to);
+	for(i=0; i<nreq; i++)
+		if(runestrcmp(req[i].name, from) == 0){
+			free(req[i].name);
+			req[i].name = erunestrdup(to);
+			return;
+		}
+}
+
+void
+addesc(Rune r, int (*f)(void), int mode)
+{
+	Esc *e;
+	
+	if(nesc >= nelem(esc)){
+		fprint(2, "too many escapes\n");
+		return;
+	}
+	e = &esc[nesc++];
+	e->r = r;
+	e->f = f;
+	e->mode = mode;
+}
+
+/*
+ * Get the next logical character in the input stream.
+ */
+int
+getnext(void)
+{
+	int i, r;
+
+next:
+	r = getrune();
+	if(r < 0)
+		return -1;
+	if(r == Uformatted){
+		br();
+		assert(!isoutput);
+		while((r = getrune()) >= 0 && r != Uunformatted){
+			if(r == Uformatted)
+				continue;
+			outrune(r);
+		}
+		goto next;
+	}
+	if(r == Uunformatted)
+		goto next;
+	if(r == backslash){
+		r = getrune();
+		if(r < 0)
+			return -1;
+		for(i=0; i<nesc; i++){
+			if(r == esc[i].r && (inputmode&esc[i].mode)==inputmode){
+				if(esc[i].f == e_warn)
+					warn("ignoring %C%C", backslash, r);
+				r = esc[i].f();
+				if(r <= 0)
+					goto next;
+				return r;
+			}
+		}
+		if(inputmode&(ArgMode|CopyMode)){
+			ungetrune(r);
+			r = backslash;
+		}
+	}
+	return r;
+}
+
+void
+ungetnext(Rune r)
+{
+	/*
+	 * really we want to undo the getrunes that led us here,
+	 * since the call after ungetnext might be getrune!
+	 */
+	ungetrune(r);
+}
+
+int
+_readx(Rune *p, int n, int nmode, int line)
+{
+	int c, omode;
+	Rune *e;
+
+	while((c = getrune()) == ' ' || c == '\t')
+		;
+	ungetrune(c);
+	omode = inputmode;
+	inputmode = nmode;
+	e = p+n-1;
+	for(c=getnext(); p<e; c=getnext()){
+		if(c < 0)
+			break;
+		if(!line && (c == ' ' || c == '\t'))
+			break;
+		if(c == '\n'){
+			if(!line)
+				ungetnext(c);
+			break;
+		}
+		*p++ = c;
+	}
+	inputmode = omode;
+	*p = 0;
+	if(c < 0)
+		return -1;
+	return 0;
+}
+
+/*
+ * Get the next argument from the current line.
+ */
+Rune*
+copyarg(void)
+{
+	static Rune buf[MaxLine];
+	int c;
+	Rune *r;
+	
+	if(_readx(buf, sizeof buf, ArgMode, 0) < 0)
+		return nil;
+	r = runestrstr(buf, L("\\\""));
+	if(r){
+		*r = 0;
+		while((c = getrune()) >= 0 && c != '\n')
+			;
+		ungetrune('\n');
+	}
+	r = erunestrdup(buf);	
+	return r;
+}
+
+/*
+ * Read the current line in given mode.  Newline not kept.
+ * Uses different buffer from copyarg!
+ */
+Rune*
+readline(int m)
+{
+	static Rune buf[MaxLine];
+	Rune *r;
+
+	if(_readx(buf, sizeof buf, m, 1) < 0)
+		return nil;
+	r = erunestrdup(buf);
+	return r;
+}
+
+/*
+ * Given the argument line (already read in copy+arg mode),
+ * parse into arguments.  Note that \" has been left in place
+ * during copy+arg mode parsing, so comments still need to be stripped.
+ */
+int
+parseargs(Rune *p, Rune **argv)
+{
+	int argc;
+	Rune *w;
+
+	for(argc=0; argc<MAXARG; argc++){
+		while(*p == ' ' || *p == '\t')
+			p++;
+		if(*p == 0)
+			break;
+		argv[argc] = p;
+		if(*p == '"'){
+			/* quoted argument */
+			if(*(p+1) == '"'){
+				/* empty argument */
+				*p = 0;
+				p += 2;
+			}else{
+				/* parse quoted string */
+				w = p++;
+				for(; *p; p++){
+					if(*p == '"' && *(p+1) == '"')
+						*w++ = '"';
+					else if(*p == '"'){
+						p++;
+						break;
+					}else
+						*w++ = *p;
+				}
+				*w = 0;
+			}	
+		}else{
+			/* unquoted argument - need to watch out for \" comment */
+			for(; *p; p++){
+				if(*p == ' ' || *p == '\t'){
+					*p++ = 0;
+					break;
+				}
+				if(*p == '\\' && *(p+1) == '"'){
+					*p = 0;
+					if(p != argv[argc])
+						argc++;
+					return argc;
+				}
+			}
+		}
+	}
+	return argc;
+}
+
+/*
+ * Process a dot line.  The dot has been read.
+ */
+void
+dotline(int dot)
+{
+	int argc, i;
+	Rune *a, *argv[1+MAXARG];
+
+	/*
+	 * Read request/macro name
+	 */
+	a = copyarg();
+	if(a == nil || a[0] == 0){
+		free(a);
+		getrune();	/* \n */
+		return;
+	}
+	argv[0] = a;
+	/*
+	 * Check for .if, .ie, and others with special parsing.
+	 */
+	for(i=0; i<nraw; i++){
+		if(runestrcmp(raw[i].name, a) == 0){
+			raw[i].f(raw[i].name);
+			free(a);
+			return;
+		}	
+	}
+
+	/*
+	 * Read rest of line in copy mode, invoke regular request.
+	 */
+	a = readline(ArgMode);
+	if(a == nil){
+		free(argv[0]);
+		return;
+	}
+	argc = 1+parseargs(a, argv+1);
+	for(i=0; i<nreq; i++){
+		if(runestrcmp(req[i].name, argv[0]) == 0){
+			if(req[i].argc != -1){
+				if(argc < 1+req[i].argc){
+					warn("not enough arguments for %C%S", dot, req[i].name);
+					free(argv[0]);
+					free(a);
+					return;
+				}
+				if(argc > 1+req[i].argc)
+					warn("too many arguments for %C%S", dot, req[i].name);
+			}
+			req[i].f(argc, argv);
+			free(argv[0]);
+			free(a);
+			return;
+		}
+	}
+
+	/*
+	 * Invoke user-defined macros.
+	 */
+	runmacro(dot, argc, argv);
+	free(argv[0]);
+	free(a);
+}
+
+/*
+ * newlines are magical in various ways.
+ */
+int bol;
+void
+newline(void)
+{
+	int n;
+
+	if(bol)
+		sp(eval(L("1v")));
+	bol = 1;
+	if((n=getnr(L(".ce"))) > 0){
+		nr(L(".ce"), n-1);
+		br();
+	}
+	if(getnr(L(".fi")) == 0)
+		br();
+	outrune('\n');
+}
+
+void
+startoutput(void)
+{
+	char *align;
+	double ps, vs, lm, rm, ti;
+	Rune buf[200];
+
+	if(isoutput)
+		return;
+	isoutput = 1;
+
+	if(getnr(L(".paragraph")) == 0)
+		return;
+
+	nr(L(".ns"), 0);
+	isoutput = 1;
+	ps = getnr(L(".s"));
+	if(ps <= 1)
+		ps = 10;
+	ps /= 72.0;
+	USED(ps);
+
+	vs = getnr(L(".v"))*getnr(L(".ls")) * 1.0/UPI;
+	vs /= (10.0/72.0);	/* ps */
+	if(vs == 0)
+		vs = 1.2;
+
+	lm = (getnr(L(".o"))+getnr(L(".i"))) * 1.0/UPI;
+	ti = getnr(L(".ti")) * 1.0/UPI;
+	nr(L(".ti"), 0);
+
+	rm = 8.0 - getnr(L(".l"))*1.0/UPI - getnr(L(".o"))*1.0/UPI;
+	if(rm < 0)
+		rm = 0;
+	switch(getnr(L(".j"))){
+	default:
+	case 0:
+		align = "left";
+		break;
+	case 1:
+		align = "justify";
+		break;
+	case 3:
+		align = "center";
+		break;
+	case 5:
+		align = "right";
+		break;
+	}
+	if(getnr(L(".ce")))
+		align = "center";
+	if(!getnr(L(".margin")))
+		runesnprint(buf, nelem(buf), "<p style=\"line-height: %.1fem; text-indent: %.2fin; margin-top: 0; margin-bottom: 0; text-align: %s;\">\n",
+			vs, ti, align);
+	else
+		runesnprint(buf, nelem(buf), "<p style=\"line-height: %.1fem; margin-left: %.2fin; text-indent: %.2fin; margin-right: %.2fin; margin-top: 0; margin-bottom: 0; text-align: %s;\">\n",
+			vs, lm, ti, rm, align);
+	outhtml(buf);
+}
+void
+br(void)
+{
+	if(!isoutput)
+		return;
+	isoutput = 0;
+
+	nr(L(".dv"), 0);
+	dv(0);
+	hideihtml();
+	if(getnr(L(".paragraph")))
+		outhtml(L("</p>"));
+}
+
+void
+r_margin(int argc, Rune **argv)
+{
+	USED(argc);
+
+	nr(L(".margin"), eval(argv[1]));
+}
+
+int inrequest;
+void
+runinput(void)
+{
+	int c;
+	
+	bol = 1;
+	for(;;){
+		c = getnext();
+		if(c < 0)
+			break;
+		if((c == dot || c == tick) && bol){
+			inrequest = 1;
+			dotline(c);
+			bol = 1;
+			inrequest = 0;
+		}else if(c == '\n'){
+			newline();
+			itrap();
+			linepos = 0;
+		}else{
+			outtrap();
+			startoutput();
+			showihtml();
+			if(c == '\t'){
+				/* XXX do better */
+				outrune(' ');
+				while(++linepos%4)
+					outrune(' ');
+			}else{
+				outrune(c);
+				linepos++;
+			}
+			bol = 0;
+		}
+	}
+}
+
+void
+run(void)
+{
+	t1init();
+	t2init();
+	t3init();
+	t4init();
+	t5init();
+	t6init();
+	t7init();
+	t8init();
+	/* t9init(); t9.c */
+	t10init();
+	t11init();
+	/* t12init(); t12.c */
+	t13init();
+	t14init();
+	t15init();
+	t16init();
+	t17init();
+	t18init();
+	t19init();
+	t20init();
+	htmlinit();
+	hideihtml();
+	
+	addreq(L("margin"), r_margin, 1);
+	nr(L(".margin"), 1);
+	nr(L(".paragraph"), 1);
+
+	runinput();
+	while(popinput())
+		;
+	dot = '.';
+	if(verbose)
+		fprint(2, "eof\n");
+	runmacro1(L("eof"));
+	closehtml();
+}
+
+void
+out(Rune *s)
+{
+	if(s == nil)
+		return;
+	for(; *s; s++)
+		outrune(*s);
+}
+
+void (*outcb)(Rune);
+
+void
+inroman(Rune r)
+{
+	int f;
+	
+	f = getnr(L(".f"));
+	nr(L(".f"), 1);
+	runmacro1(L("font"));
+	outrune(r);
+	nr(L(".f"), f);
+	runmacro1(L("font"));
+}
+
+void
+Brune(Rune r)
+{
+	if(r == '&')
+		Bprint(&bout, "&amp;");
+	else if(r == '<')
+		Bprint(&bout, "&lt;");
+	else if(r == '>')
+		Bprint(&bout, "&gt;");
+	else if(r < Runeself || utf8)
+		Bprint(&bout, "%C", r);
+	else
+		Bprint(&bout, "%S", rune2html(r));
+}
+
+void
+outhtml(Rune *s)
+{
+	Rune r;
+	
+	for(; *s; s++){
+		switch(r = *s){
+		case '<':
+			r = Ult;
+			break;
+		case '>':
+			r = Ugt;
+			break;
+		case '&':
+			r = Uamp;
+			break;
+		case ' ':
+			r = Uspace;
+			break;
+		}
+		outrune(r);
+	}
+}
+
+void
+outrune(Rune r)
+{
+	switch(r){
+	case ' ':
+		if(getnr(L(".fi")) == 0)
+			r = Unbsp;
+		break;
+	case Uformatted:
+	case Uunformatted:
+		abort();
+	}
+	if(outcb){
+		if(r == ' ')
+			r = Uspace;
+		outcb(r);
+		return;
+	}
+	/* writing to bout */
+	switch(r){
+	case Uempty:
+		return;
+	case Upl:
+		inroman('+');
+		return;
+	case Ueq:
+		inroman('=');
+		return;
+	case Umi:
+		inroman(0x2212);
+		return;
+	case Utick:
+		r = '\'';
+		break;
+	case Ubtick:
+		r = '`';
+		break;
+	case Uminus:
+		r = '-';
+		break;
+	case '\'':
+		Bprint(&bout, "&rsquo;");
+		return;
+	case '`':
+		Bprint(&bout, "&lsquo;");
+		return;
+	case Uamp:
+		Bputrune(&bout, '&');
+		return;
+	case Ult:
+		Bputrune(&bout, '<');
+		return;
+	case Ugt:
+		Bputrune(&bout, '>');
+		return;
+	case Uspace:
+		Bputrune(&bout, ' ');
+		return;
+	case 0x2032:
+		/*
+		 * In Firefox, at least, the prime is not
+		 * a superscript by default.
+		 */
+		Bprint(&bout, "<sup>");
+		Brune(r);
+		Bprint(&bout, "</sup>");
+		return;
+	}
+	Brune(r);
+}
+
+void
+r_nop(int argc, Rune **argv)
+{
+	USED(argc);
+	USED(argv);
+}
+
+void
+r_warn(int argc, Rune **argv)
+{
+	USED(argc);
+	warn("ignoring %C%S", dot, argv[0]);
+}
+
+int
+e_warn(void)
+{
+	/* dispatch loop prints a warning for us */
+	return 0;
+}
+
+int
+e_nop(void)
+{
+	return 0;
+}
blob - /dev/null
blob + 8236694de7c081d02882eb9b403244632fcda360 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t1.c
@@ -0,0 +1,186 @@
+#include "a.h"
+ 
+/*
+ * Section 1 - General Explanation.
+ */
+
+/* 1.3 - Numerical parameter input.  */
+char *units = "icPmnpuvx";
+int
+scale2units(char c)
+{
+	int x;
+	
+	switch(c){
+	case 'i':	/* inch */
+		return UPI;
+	case 'c':	/* centimeter */
+		return 0.3937008 * UPI;
+	case 'P':	/* pica = 1/6 inch */
+		return UPI / 6;
+	case 'm':	/* em = S points */
+		return UPI / 72.0 * getnr(L(".s"));
+	case 'n':	/* en = em/2 */
+		return UPI / 72.0 * getnr(L(".s")) / 2;
+	case 'p':	/* point = 1/72 inch */
+		return UPI / 72;
+	case 'u':	/* basic unit */
+		return 1;
+	case 'v':	/* vertical line space V */
+		x = getnr(L(".v"));
+		if(x == 0)
+			x = 12 * UPI / 72;
+		return x;
+	case 'x':	/* pixel (htmlroff addition) */
+		return UPX;
+	default:
+		return 1;
+	}
+}
+
+/* 1.4 - Numerical expressions. */
+int eval0(Rune**, int, int);
+int
+eval(Rune *s)
+{
+	return eval0(&s, 1, 1);
+}
+long
+runestrtol(Rune *a, Rune **p)
+{
+	long n;
+	
+	n = 0;
+	while('0' <= *a && *a <= '9'){
+		n = n*10 + *a-'0';
+		a++;
+	}
+	*p = a;
+	return n;
+}
+
+int
+evalscale(Rune *s, int c)
+{
+	return eval0(&s, scale2units(c), 1);
+}
+
+int
+eval0(Rune **pline, int scale, int recur)
+{
+	Rune *p;
+	int neg;
+	double f, p10;
+	int x, y;
+
+	neg = 0;
+	p = *pline;
+	while(*p == '-'){
+		neg = 1 - neg;
+		p++;
+	}
+	if(*p == '('){
+		p++;
+		x = eval0(&p, scale, 1);
+		if (*p != ')'){
+			*pline = p;
+			return x;
+		}
+		p++;
+	}else{
+		f = runestrtol(p, &p);
+		if(*p == '.'){
+			p10 = 1.0;
+			p++;
+			while('0' <= *p && *p <= '9'){
+				p10 /= 10;
+				f += p10*(*p++ - '0');
+			}
+		}
+		if(*p && strchr(units, *p)){
+			if(scale)
+				f *= scale2units(*p);
+			p++;
+		}else if(scale)
+			f *= scale;
+		x = f;
+	}
+	if(neg)
+		x = -x;
+	if(!recur){
+		*pline = p;
+		return x;
+	}
+	
+	while(*p){
+		switch(*p++) {
+		case '+':
+			x += eval0(&p, scale, 0);
+			continue;
+		case '-':
+			x -= eval0(&p, scale, 0);
+			continue;
+		case '*':
+			x *= eval0(&p, scale, 0);
+			continue;
+		case '/':
+			y = eval0(&p, scale, 0);
+			if (y == 0) {
+				fprint(2, "%L: divide by zero %S\n", p);
+				y = 1;
+			}
+			x /= y;
+			continue;
+		case '%':
+			y = eval0(&p, scale, 0);
+			if (!y) {
+				fprint(2, "%L: modulo by zero %S\n", p);
+				y = 1;
+			}
+			x %= y;
+			continue;
+		case '<':
+			if (*p == '=') {
+				p++;
+				x = x <= eval0(&p, scale, 0);
+				continue;
+			}
+			x = x < eval0(&p, scale, 0);
+			continue;
+		case '>':
+			if (*p == '=') {
+				p++;
+				x = x >= eval0(&p, scale, 0);
+				continue;
+			}
+			x = x > eval0(&p, scale, 0);
+			continue;
+		case '=':
+			if (*p == '=')
+				p++;
+			x = x == eval0(&p, scale, 0);
+			continue;
+		case '&':
+			x &= eval0(&p, scale, 0);
+			continue;
+		case ':':
+			x |= eval0(&p, scale, 0);
+			continue;
+		}
+	}
+	*pline = p;
+	return x;
+}
+
+void
+t1init(void)
+{
+	Tm tm;
+	
+	tm = *localtime(time(0));
+	nr(L("dw"), tm.wday+1);
+	nr(L("dy"), tm.mday);
+	nr(L("mo"), tm.mon);
+	nr(L("yr"), tm.year%100);
+}
+
blob - /dev/null
blob + e029db35fd7a8a52f24bf9f66d49bf1e3fc1c702 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t10.c
@@ -0,0 +1,140 @@
+#include "a.h"
+
+/*
+ * 10. Input and Output Conventions and Character Translation.
+ */
+
+/* set escape character */
+void
+r_ec(int argc, Rune **argv)
+{
+	if(argc == 1)
+		backslash = '\\';
+	else
+		backslash = argv[1][0];
+}
+
+/* turn off escape character */
+void
+r_eo(int argc, Rune **argv)
+{
+	USED(argc);
+	USED(argv);
+	backslash = -2;
+}
+
+/* continuous underline (same as ul in troff) for the next N lines */
+/* set underline font */
+void
+g_uf(int argc, Rune **argv)
+{
+	USED(argc);
+	USED(argv);
+}
+
+/* set control character */
+void
+r_cc(int argc, Rune **argv)
+{
+	if(argc == 1)
+		dot = '.';
+	else
+		dot = argv[1][0];
+}
+
+/* set no-break control character */
+void
+r_c2(int argc, Rune **argv)
+{
+	if(argc == 1)
+		tick = '\'';
+	else
+		tick = argv[1][0];
+}
+
+/* output translation */
+
+int
+e_bang(void)
+{
+	Rune *line;
+	
+	line = readline(CopyMode);
+	out(line);
+	outrune('\n');
+	free(line);
+	return 0;
+}
+
+int
+e_X(void)
+{
+	int c;
+	
+	while((c = getrune()) >= 0 && c != '\'' && c != '\n')
+		outrune(c);
+	if(c == '\n'){
+		warn("newline in %CX'...'", backslash);
+		outrune(c);
+	}
+	if(c < 0)
+		warn("eof in %CX'...'", backslash);
+	return 0;
+}
+
+int
+e_quote(void)
+{
+	int c;
+
+	if(inputmode&ArgMode){
+		/* Leave \" around for argument parsing */
+		ungetrune('"');
+		return '\\';
+	}
+	while((c = getrune()) >= 0 && c != '\n')
+		;
+	return '\n';
+}
+
+int
+e_newline(void)
+{
+	return 0;
+}
+
+int
+e_e(void)
+{
+	return backslash;
+}
+
+void
+r_comment(Rune *name)
+{
+	int c;
+	
+	USED(name);
+	while((c = getrune()) >= 0 && c != '\n')
+		;
+}
+
+void
+t10init(void)
+{
+	addreq(L("ec"), r_ec, -1);
+	addreq(L("eo"), r_eo, 0);
+	addreq(L("lg"), r_nop, -1);
+	addreq(L("cc"), r_cc, -1);
+	addreq(L("c2"), r_c2, -1);
+	addreq(L("tr"), r_warn, -1);
+	addreq(L("ul"), r_nop, -1);
+	addraw(L("\\\""), r_comment);
+	
+	addesc('!', e_bang, 0);
+	addesc('X', e_X, 0);
+	addesc('\"', e_quote, CopyMode|ArgMode);
+	addesc('\n', e_newline, CopyMode|ArgMode|HtmlMode);
+	addesc('e', e_e, 0);
+}
+
blob - /dev/null
blob + 53d68aac08dcd82f0c73652daf5ef413713ad6e4 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t11.c
@@ -0,0 +1,107 @@
+#include "a.h"
+
+/*
+ * 11. Local Horizontal and Vertical Motions, and the Width Function.
+ */
+
+int
+e_0(void)
+{
+	/* digit-width space */
+	return ' ';
+}
+
+int
+dv(int d)
+{
+	Rune sub[6];
+
+	d += getnr(L(".dv"));
+	nr(L(".dv"), d);
+
+	runestrcpy(sub, L("<sub>"));
+	sub[0] = Ult;
+	sub[4] = Ugt;
+	if(d < 0){
+		sub[3] = 'p';
+		ihtml(L(".dv"), sub);
+	}else if(d > 0)
+		ihtml(L(".dv"), sub);
+	else
+		ihtml(L(".dv"), nil);
+	return 0;
+}
+
+int
+e_v(void)
+{
+	dv(eval(getqarg()));
+	return 0;
+}
+
+int
+e_u(void)
+{
+	dv(eval(L("-0.5m")));
+	return 0;
+}
+
+int
+e_d(void)
+{
+	dv(eval(L("0.5m")));
+	return 0;
+}
+
+int
+e_r(void)
+{
+	dv(eval(L("-1m")));
+	return 0;
+}
+
+int
+e_h(void)
+{
+	getqarg();
+	return 0;
+}
+
+int
+e_w(void)
+{
+	Rune *a;
+	Rune buf[40];
+	
+	a = getqarg();
+	runesnprint(buf, sizeof buf, "%ld", runestrlen(a));
+	pushinputstring(buf);
+	nr(L("st"), 0);
+	nr(L("sb"), 0);
+	nr(L("ct"), 0);
+	return 0;
+}
+
+int
+e_k(void)
+{
+	getname();
+	warn("%Ck not available", backslash);
+	return 0;
+}
+
+void
+t11init(void)
+{
+	addesc('|', e_nop, 0);
+	addesc('^', e_nop, 0);
+	addesc('v', e_v, 0);
+	addesc('h', e_h, 0);
+	addesc('w', e_w, 0);
+	addesc('0', e_0, 0);
+	addesc('u', e_u, 0);
+	addesc('d', e_d, 0);
+	addesc('r', e_r, 0);
+	addesc('k', e_k, 0);
+}
+
blob - /dev/null
blob + 5ec577d353c56cad1033cfcfaa9f3549d7d52ca0 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t12.c
@@ -0,0 +1,67 @@
+#include "a.h"
+
+/*
+ * 12. Overstrike, bracket, line-drawing, graphics, and zero-width functions.
+ */
+
+/*
+	\o'asdf'
+	\zc
+	\b'asdf'
+	\l'Nc'
+	\L'Nc'
+	\D'xxx'
+*/
+
+int
+e_o(void)
+{
+	pushinputstring(getqarg());
+	return 0;
+}
+
+int
+e_z(void)
+{
+	getnext();
+	return 0;
+}
+
+int
+e_b(void)
+{
+	pushinputstring(getqarg());
+	return 0;
+}
+
+int
+e_l(void)
+{
+	getqarg();
+	return 0;
+}
+
+int
+e_L(void)
+{
+	getqarg();
+	return 0;
+}
+
+int
+e_D(void)
+{
+	getqarg();
+	return 0;
+}
+
+void
+t12init(void)
+{
+	addesc('o', e_o, 0);
+	addesc('z', e_z, 0);
+	addesc('b', e_b, 0);
+	addesc('l', e_l, 0);
+	addesc('L', e_L, 0);
+	addesc('D', e_D, 0);
+}
blob - /dev/null
blob + 0fadab3ad296a6f1cbe2dcd29706576cdd6596b0 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t13.c
@@ -0,0 +1,17 @@
+#include "a.h"
+
+/*
+ * 13. Hyphenation.
+ */
+
+void
+t13init(void)
+{
+	addreq(L("nh"), r_nop, -1);
+	addreq(L("hy"), r_nop, -1);
+	addreq(L("hc"), r_nop, -1);
+	addreq(L("hw"), r_nop, -1);
+	
+	addesc('%', e_nop, 0);
+}
+
blob - /dev/null
blob + 1dab3516747297a15609938fe81351338c5b05f4 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t14.c
@@ -0,0 +1,33 @@
+#include "a.h"
+
+/*
+ * 14. Three-part titles.
+ */
+void
+r_lt(int argc, Rune **argv)
+{
+	Rune *p;
+	
+	if(argc < 2)
+		nr(L(".lt"), evalscale(L("6.5i"), 'm'));
+	else{
+		if(argc > 2)
+			warn("too many arguments for .lt");
+		p = argv[1];
+		if(p[0] == '-')
+			nr(L(".lt"), getnr(L(".lt"))-evalscale(p+1, 'm'));
+		else if(p[0] == '+')
+			nr(L(".lt"), getnr(L(".lt"))+evalscale(p+1, 'm'));
+		else
+			nr(L(".lt"), evalscale(p, 'm'));
+	}
+}
+
+void
+t14init(void)
+{
+	addreq(L("tl"), r_warn, -1);
+	addreq(L("pc"), r_nop, -1);	/* page number char */
+	addreq(L("lt"), r_lt, -1);
+}
+
blob - /dev/null
blob + fbfd5128fc2cb642890ea0cb1f425f82a8a376a2 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t15.c
@@ -0,0 +1,13 @@
+#include "a.h"
+
+/*
+ * 15. Output line numbering.
+ */
+
+void
+t15init(void)
+{
+	addreq(L("nm"), r_warn, -1);
+	addreq(L("nn"), r_warn, -1);
+}
+
blob - /dev/null
blob + 3a9c427e8a4e1d52ec8dac1815f8c832ad6c8020 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t16.c
@@ -0,0 +1,156 @@
+#include "a.h"
+
+/*
+ * 16. Conditional acceptance of input.
+ *
+ *	conditions are
+ *		c - condition letter (o, e, t, n)
+ *		!c - not c
+ *		N - N>0
+ *		!N - N <= 0
+ *		'a'b' - if a==b
+ *		!'a'b'	- if a!=b
+ *
+ *	\{xxx\} can be used for newline in bodies
+ *
+ *	.if .ie .el
+ *
+ */
+
+int iftrue[20];
+int niftrue;
+
+void
+startbody(void)
+{
+	int c;
+			
+	while((c = getrune()) == ' ' || c == '\t')
+		;
+	ungetrune(c);
+}
+
+void
+skipbody(void)
+{
+	int c, cc, nbrace;
+
+	nbrace = 0;
+	for(cc=0; (c = getrune()) >= 0; cc=c){
+		if(c == '\n' && nbrace <= 0)
+			break;
+		if(cc == '\\' && c == '{')
+			nbrace++;
+		if(cc == '\\' && c == '}')
+			nbrace--;
+	}
+}
+
+int
+ifeval(void)
+{
+	int c, cc, neg, nc;
+	Rune line[MaxLine], *p, *e, *q;
+	Rune *a;
+	
+	while((c = getnext()) == ' ' || c == '\t')
+		;
+	neg = 0;
+	while(c == '!'){
+		neg = !neg;
+		c = getnext();
+	}
+
+	if('0' <= c && c <= '9'){
+		ungetnext(c);
+		a = copyarg();
+		c = (eval(a)>0) ^ neg;
+		free(a);
+		return c;
+	}
+	
+	switch(c){
+	case ' ':
+	case '\n':
+		ungetnext(c);
+		return !neg;
+	case 'o':	/* odd page */
+	case 't':	/* troff */
+	case 'h':	/* htmlroff */
+		while((c = getrune()) != ' ' && c != '\t' && c != '\n' && c >= 0)
+			;
+		return 1 ^ neg;
+	case 'n':	/* nroff */
+	case 'e':	/* even page */
+		while((c = getnext()) != ' ' && c != '\t' && c != '\n' && c >= 0)
+			;
+		return 0 ^ neg;
+	}
+
+	/* string comparison 'string1'string2' */
+	p = line;
+	e = p+nelem(line);
+	nc = 0;
+	q = nil;
+	while((cc=getnext()) >= 0 && cc != '\n' && p<e){
+		if(cc == c){
+			if(++nc == 2)
+				break;
+			q = p;
+		}
+		*p++ = cc;
+	}
+	if(cc != c){
+		ungetnext(cc);
+		return 0;
+	}
+	if(nc < 2){
+		return 0;
+	}
+	*p = 0;
+	return (q-line == p-(q+1)
+		&& memcmp(line, q+1, (q-line)*sizeof(Rune))==0) ^ neg;
+}
+	
+void
+r_if(Rune *name)
+{
+	int n;
+	
+	n = ifeval();
+	if(runestrcmp(name, L("ie")) == 0){
+		if(niftrue >= nelem(iftrue))
+			sysfatal("%Cie overflow", dot);
+		iftrue[niftrue++] = n;
+	}
+	if(n)
+		startbody();
+	else
+		skipbody();
+}
+
+void
+r_el(Rune *name)
+{
+	USED(name);
+	
+	if(niftrue <= 0){
+		warn("%Cel underflow", dot);
+		return;
+	}
+	if(iftrue[--niftrue])
+		skipbody();
+	else
+		startbody();
+}
+
+void
+t16init(void)
+{
+	addraw(L("if"), r_if);
+	addraw(L("ie"), r_if);
+	addraw(L("el"), r_el);
+	
+	addesc('{', e_nop, HtmlMode|ArgMode);
+	addesc('}', e_nop, HtmlMode|ArgMode);
+}
blob - /dev/null
blob + 2800ec7655c78a681a1fd87d819b947046a68c68 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t17.c
@@ -0,0 +1,131 @@
+#include "a.h"
+
+/*
+ * 17.  Environment switching.
+ */
+typedef struct Env Env;
+struct Env
+{
+	int s;
+	int s0;
+	int f;
+	int f0;
+	int fi;
+	int ad;
+	int ce;
+	int v;
+	int v0;
+	int ls;
+	int ls0;
+	int it;
+	/* - ta */
+	/* - tc */
+	/* - lc */
+	/* - ul */
+	/* - cu */
+	/* - cc */
+	/* - c2 */
+	/* - nh */
+	/* - hy */
+	/* - hc */
+	/* - lt */
+	/* - nm */
+	/* - nn */
+	/* - mc */
+};
+
+Env defenv =
+{
+	10,
+	10,
+	1,
+	1,
+	1,
+	1,
+	0,
+	12,
+	12,
+	0,
+	0,
+	0,
+};
+
+Env env[3];
+Env *evstack[20];
+int nevstack;
+
+void
+saveenv(Env *e)
+{
+	e->s = getnr(L(".s"));
+	e->s0 = getnr(L(".s0"));
+	e->f = getnr(L(".f"));
+	e->f0 = getnr(L(".f0"));
+	e->fi = getnr(L(".fi"));
+	e->ad = getnr(L(".ad"));
+	e->ce = getnr(L(".ce"));
+	e->v = getnr(L(".v"));
+	e->v0 = getnr(L(".v0"));
+	e->ls = getnr(L(".ls"));
+	e->ls0 = getnr(L(".ls0"));
+	e->it = getnr(L(".it"));
+}
+
+void
+restoreenv(Env *e)
+{
+	nr(L(".s"), e->s);
+	nr(L(".s0"), e->s0);
+	nr(L(".f"), e->f);
+	nr(L(".f0"), e->f0);
+	nr(L(".fi"), e->fi);
+	nr(L(".ad"), e->ad);
+	nr(L(".ce"), e->ce);
+	nr(L(".v"), e->v);
+	nr(L(".v0"), e->v0);
+	nr(L(".ls"), e->ls);
+	nr(L(".ls0"), e->ls0);
+	nr(L(".it"), e->it);
+
+	nr(L(".ev"), e-env);
+	runmacro1(L("font"));
+}
+
+
+void
+r_ev(int argc, Rune **argv)
+{
+	int i;
+	Env *e;
+	
+	if(argc == 1){
+		if(nevstack <= 0){
+			if(verbose) warn(".ev stack underflow");
+			return;
+		}
+		restoreenv(evstack[--nevstack]);
+		return;
+	}
+	if(nevstack >= nelem(evstack))
+		sysfatal(".ev stack overflow");
+	i = eval(argv[1]);
+	if(i < 0 || i > 2){
+		warn(".ev bad environment %d", i);
+		i = 0;
+	}
+	e = &env[getnr(L(".ev"))];
+	saveenv(e);
+	evstack[nevstack++] = e;
+	restoreenv(&env[i]);
+}
+
+void
+t17init(void)
+{
+	int i;
+	
+	for(i=0; i<nelem(env); i++)
+		env[i] = defenv;
+
+	addreq(L("ev"), r_ev, -1);
+}
blob - /dev/null
blob + f5c74a1fe4b6a9a8bb113116fd82e7cd2ef35c4e (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t18.c
@@ -0,0 +1,67 @@
+#include "a.h"
+
+/*
+ * 18. Insertions from the standard input
+ */
+void
+r_rd(int argc, Rune **argv)
+{
+	char *s;
+	Rune *p;
+	Fmt fmt;
+	static int didstdin;
+	static Biobuf bstdin;
+	
+	/*
+	 * print prompt, then read until double newline,
+	 * then run the text just read as though it were
+	 * a macro body, using the remaining arguments.
+	 */
+	if(isatty(0)){
+		if(argc > 1)
+			fprint(2, "%S", argv[1]);
+		else
+			fprint(2, "%c", 7/*BEL*/);
+	}
+	
+	if(!didstdin){
+		Binit(&bstdin, 0, OREAD);
+		didstdin = 1;
+	}
+	runefmtstrinit(&fmt);
+	while((s = Brdstr(&bstdin, '\n', 0)) != nil){
+		if(s[0] == '\n'){
+			free(s);
+			break;
+		}
+		fmtprint(&fmt, "%s", s);
+		free(s);
+	}
+	p = runefmtstrflush(&fmt);
+	if(p == nil)
+		warn("out of memory in %Crd", dot);
+	ds(L(".rd"), p);
+	argc--;
+	argv++;
+	argv[0] = L(".rd");
+	runmacro('.', argc, argv);
+	ds(L(".rd"), nil);
+}
+
+/* terminate exactly as if input had ended */
+void
+r_ex(int argc, Rune **argv)
+{
+	USED(argc);
+	USED(argv);
+	
+	while(popinput())
+		;
+}
+
+void
+t18init(void)
+{
+	addreq(L("rd"), r_rd, -1);
+	addreq(L("ex"), r_ex, 0);
+}
blob - /dev/null
blob + a4cc18f443d9c8516e72e8b0d3d5b9172966eaaa (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t19.c
@@ -0,0 +1,142 @@
+#include "a.h"
+
+/*
+ * 19. Input/output file switching.
+ */
+
+/* .so - push new source file */
+void
+r_so(int argc, Rune **argv)
+{
+	USED(argc);
+	pushinputfile(erunesmprint("%s", unsharp(esmprint("%S", argv[1]))));
+}
+
+/* .nx - end this file, switch to arg */
+void
+r_nx(int argc, Rune **argv)
+{
+	int n;
+	
+	if(argc == 1){
+		while(popinput())
+			;
+	}else{
+		if(argc > 2)
+			warn("too many arguments for .nx");
+		while((n=popinput()) && n != 2)
+			;
+		pushinputfile(argv[1]);
+	}
+}
+
+/* .sy - system: run string */
+void
+r_sy(Rune *name)
+{
+	USED(name);
+	warn(".sy not implemented");
+}
+
+/* .pi - pipe output to string */
+void
+r_pi(Rune *name)
+{
+	USED(name);
+	warn(".pi not implemented");
+}
+
+/* .cf - copy contents of filename to output */
+void
+r_cf(int argc, Rune **argv)
+{
+	int c;
+	char *p;
+	Biobuf *b;
+
+	USED(argc);
+	p = esmprint("%S", argv[1]);
+	if((b = Bopen(p, OREAD)) == nil){
+		fprint(2, "%L: open %s: %r\n", p);
+		free(p);
+		return;
+	}
+	free(p);
+
+	while((c = Bgetrune(b)) >= 0)
+		outrune(c);
+	Bterm(b);
+}
+
+void
+r_inputpipe(Rune *name)
+{
+	Rune *cmd, *stop, *line;
+	int n, pid, p[2], len;
+	Waitmsg *w;
+	
+	USED(name);
+	if(pipe(p) < 0){
+		warn("pipe: %r");
+		return;
+	}
+	stop = copyarg();
+	cmd = readline(CopyMode);
+	pid = fork();
+	switch(pid){
+	case 0:
+		if(p[0] != 0){
+			dup(p[0], 0);
+			close(p[0]);
+		}
+		close(p[1]);
+		execl(unsharp("#9/bin/rc"), "rc", "-c", esmprint("%S", cmd), nil);
+		warn("%Cdp %S: %r", dot, cmd);
+		_exits(nil);
+	case -1:
+		warn("fork: %r");
+	default:
+		close(p[0]);
+		len = runestrlen(stop);
+		fprint(p[1], ".ps %d\n", getnr(L(".s")));
+		fprint(p[1], ".vs %du\n", getnr(L(".v")));
+		fprint(p[1], ".ft %d\n", getnr(L(".f")));
+		fprint(p[1], ".ll 8i\n");
+		fprint(p[1], ".pl 30i\n");
+		while((line = readline(~0)) != nil){
+			if(runestrncmp(line, stop, len) == 0 
+			&& (line[len]==' ' || line[len]==0 || line[len]=='\t'
+				|| (line[len]=='\\' && line[len+1]=='}')))
+				break;
+			n = runestrlen(line);
+			line[n] = '\n';
+			fprint(p[1], "%.*S", n+1, line);
+			free(line);
+		}
+		free(stop);
+		close(p[1]);
+		w = wait();
+		if(w == nil){
+			warn("wait: %r");
+			return;
+		}
+		if(w->msg[0])
+			sysfatal("%C%S %S: %s", dot, name, cmd, w->msg);
+		free(cmd);
+		free(w);
+	}
+}	
+
+void
+t19init(void)
+{
+	addreq(L("so"), r_so, 1);
+	addreq(L("nx"), r_nx, -1);
+	addraw(L("sy"), r_sy);
+	addraw(L("inputpipe"), r_inputpipe);
+	addraw(L("pi"), r_pi);
+	addreq(L("cf"), r_cf, 1);
+	
+	nr(L("$$"), getpid());
+}
+
blob - /dev/null
blob + 54481d0a56ac33145ef8a922bcff3c424c5460f2 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t2.c
@@ -0,0 +1,274 @@
+#include "a.h"
+
+/*
+ * Section 2 - Font and character size control.
+ */
+ 
+/* 2.1 - Character set */
+/* XXX
+ *
+ * \C'name' - character named name
+ * \N'n' - character number
+ * \(xx - two-letter character
+ * \- 
+ * \`
+ * \'
+ * `
+ * '
+ * -
+ */
+
+Rune*
+getqarg(void)
+{
+	static Rune buf[MaxLine];
+	int c;
+	Rune *p, *e;
+	
+	p = buf;
+	e = p+sizeof buf-1;
+	
+	if(getrune() != '\'')
+		return nil;
+	while(p < e){
+		c = getrune();
+		if(c < 0)
+			return nil;
+		if(c == '\'')
+			break;
+		*p++ = c;
+	}
+	*p = 0;
+	return buf;
+}
+
+int
+e_N(void)
+{
+	Rune *a;
+	if((a = getqarg()) == nil)
+		goto error;
+	return eval(a);
+
+error:
+	warn("malformed %CN'...'", backslash);
+	return 0;
+}
+
+int
+e_paren(void)
+{
+	int c, cc;
+	Rune buf[2], r;
+	
+	if((c = getrune()) < 0 || c == '\n')
+		goto error;
+	if((cc = getrune()) < 0 || cc == '\n')
+		goto error;
+	buf[0] = c;
+	buf[1] = cc;
+	r = troff2rune(buf);
+ 	if(r == Runeerror)
+		warn("unknown char %C(%C%C", backslash, c, cc);
+	return r;
+	
+error:
+	warn("malformed %C(xx", backslash);
+	return 0;
+}
+
+/* 2.2 - Fonts */
+Rune fonttab[10][100];
+
+/*
+ * \fx \f(xx \fN - font change
+ * number register .f - current font
+ * \f0 previous font (undocumented?)
+ */
+/* change to font f.  also \fx, \f(xx, \fN */
+/* .ft LongName is okay - temporarily at fp 0 */
+void
+ft(Rune *f)
+{
+	int i;
+	int fn;
+	
+	if(f && runestrcmp(f, L("P")) == 0)
+		f = nil;
+	if(f == nil)
+		fn = 0;
+	else if(isdigit(f[0]))
+		fn = eval(f);
+	else{
+		for(i=0; i<nelem(fonttab); i++){
+			if(runestrcmp(fonttab[i], f) == 0){
+				fn = i;
+				goto have;
+			}
+		}
+		warn("unknown font %S", f);
+		fn = 1;
+	}
+have:
+	if(fn < 0 || fn >= nelem(fonttab)){
+		warn("unknown font %d", fn);
+		fn = 1;
+	}
+	if(fn == 0)
+		fn = getnr(L(".f0"));
+	nr(L(".f0"), getnr(L(".f")));
+	nr(L(".f"), fn);
+	runmacro1(L("font"));
+}
+
+/* mount font named f on physical position N */
+void
+fp(int i, Rune *f)
+{
+	if(i <= 0 || i >= nelem(fonttab)){
+		warn("bad font position %d", i);
+		return;
+	}
+	runestrecpy(fonttab[i], fonttab[i]+sizeof fonttab[i], f);
+}
+	
+int
+e_f(void)
+{
+	ft(getname());
+	return 0;
+}
+
+void
+r_ft(int argc, Rune **argv)
+{
+	if(argc == 1)
+		ft(nil);
+	else
+		ft(argv[1]);
+}
+
+void
+r_fp(int argc, Rune **argv)
+{
+	if(argc < 3){
+		warn("missing arguments to %Cfp", dot);
+		return;
+	}
+	fp(eval(argv[1]), argv[2]);
+}
+
+/* 2.3 - Character size */
+
+/* \H'±N' sets height */
+
+void
+ps(int s)
+{
+	if(s == 0)
+		s = getnr(L(".s0"));
+	nr(L(".s0"), getnr(L(".s")));
+	nr(L(".s"), s);
+	runmacro1(L("font"));
+}
+
+/* set point size */
+void
+r_ps(int argc, Rune **argv)
+{
+	Rune *p;
+	
+	if(argc == 1 || argv[1][0] == 0)
+		ps(0);
+	else{
+		p = argv[1];
+		if(p[0] == '-')
+			ps(getnr(L(".s"))-eval(p+1));
+		else if(p[0] == '+')
+			ps(getnr(L(".s"))+eval(p+1));
+		else
+			ps(eval(p));
+	}
+}
+
+int
+e_s(void)
+{
+	int c, cc, ccc, n, twodigit;
+	
+	c = getnext();
+	if(c < 0)
+		return 0;
+	if(c == '+' || c == '-'){
+		cc = getnext();
+		if(cc == '('){
+			cc = getnext();
+			ccc = getnext();
+			if(cc < '0' || cc > '9' || ccc < '0' || ccc > '9'){
+				warn("bad size %Cs%C(%C%C", backslash, c, cc, ccc);
+				return 0;
+			}
+			n = (cc-'0')*10+ccc-'0';
+		}else{
+			if(cc < '0' || cc > '9'){
+				warn("bad size %Cs%C%C", backslash, c, cc);
+				return 0;
+			}
+			n = cc-'0';
+		}
+		if(c == '+')
+			ps(getnr(L(".s"))+n);
+		else
+			ps(getnr(L(".s"))-n);
+		return 0;
+	}
+	twodigit = 0;
+	if(c == '('){
+		twodigit = 1;
+		c = getnext();
+		if(c < 0)
+			return 0;
+	}
+	if(c < '0' || c > '9'){
+		warn("bad size %Cs%C", backslash, c);
+		ungetnext(c);
+		return 0;
+	}
+	if(twodigit || (c < '4' && c != '0')){
+		cc = getnext();
+		if(c < 0)
+			return 0;
+		n = (c-'0')*10+cc-'0';
+	}else
+		n = c-'0';
+	ps(n);
+	return 0;
+}
+
+void
+t2init(void)
+{
+	fp(1, L("R"));
+	fp(2, L("I"));
+	fp(3, L("B"));
+	fp(4, L("BI"));
+	fp(5, L("CW"));
+	
+	nr(L(".s"), 10);
+	nr(L(".s0"), 10);
+
+	addreq(L("ft"), r_ft, -1);
+	addreq(L("fp"), r_fp, -1);
+	addreq(L("ps"), r_ps, -1);
+	addreq(L("ss"), r_warn, -1);
+	addreq(L("cs"), r_warn, -1);
+	addreq(L("bd"), r_warn, -1);
+
+	addesc('f', e_f, 0);
+	addesc('s', e_s, 0);
+	addesc('(', e_paren, 0);	/* ) */
+	addesc('C', e_warn, 0);
+	addesc('N', e_N, 0);
+	/* \- \' \` are handled in html.c */
+}
+
blob - /dev/null
blob + 62ea914fc04a0994d371db81aa6d80823615932e (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t20.c
@@ -0,0 +1,79 @@
+#include "a.h"
+
+/*
+ * 20. Miscellaneous
+ */
+
+/* .mc - margin character */
+/* .ig - ignore; treated like a macro in t7.c */
+
+/* .pm - print macros and strings */
+
+void
+r_pm(int argc, Rune **argv)
+{
+	int i;
+	
+	if(argc == 1){
+		printds(0);
+		return;
+	}
+	if(runestrcmp(argv[1], L("t")) == 0){
+		printds(1);
+		return;
+	}
+	for(i=1; i<argc; i++)
+		fprint(2, "%S: %S\n", argv[i], getds(argv[i]));
+}
+
+void
+r_tm(Rune *name)
+{
+	Rune *line;
+	
+	USED(name);
+	
+	line = readline(CopyMode);
+	fprint(2, "%S\n", line);
+	free(line);
+}
+
+void
+r_ab(Rune *name)
+{
+	USED(name);
+	
+	r_tm(L("ab"));
+	exits(".ab");
+}
+
+void
+r_lf(int argc, Rune **argv)
+{
+	if(argc == 1)
+		return;
+	if(argc == 2)
+		setlinenumber(nil, eval(argv[1]));
+	if(argc == 3)
+		setlinenumber(argv[2], eval(argv[1]));
+}
+
+void
+r_fl(int argc, Rune **argv)
+{
+	USED(argc);
+	USED(argv);
+	Bflush(&bout);
+}
+
+void
+t20init(void)
+{
+	addreq(L("mc"), r_warn, -1);
+	addraw(L("tm"), r_tm);
+	addraw(L("ab"), r_ab);
+	addreq(L("lf"), r_lf, -1);
+	addreq(L("pm"), r_pm, -1);
+	addreq(L("fl"), r_fl, 0);
+}
+
blob - /dev/null
blob + e54573c13b51b8ec618d580dc2f12b45623d458a (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t3.c
@@ -0,0 +1,49 @@
+#include "a.h"
+
+/*
+ * Section 3 - page control (mostly irrelevant).
+ */
+
+/* page offset */
+void
+po(int o)
+{
+	nr(L(".o0"), getnr(L(".o")));
+	nr(L(".o"), o);
+}
+
+void
+r_po(int argc, Rune **argv)
+{
+	if(argc == 1){
+		po(getnr(L(".o0")));
+		return;
+	}
+	if(argv[1][0] == '+')
+		po(getnr(L(".o"))+evalscale(argv[1]+1, 'v'));
+	else if(argv[1][0] == '-')
+		po(getnr(L(".o"))-evalscale(argv[1]+1, 'v'));
+	else
+		po(evalscale(argv[1], 'v'));
+}
+
+/* .ne - need vertical space */
+/* .mk - mark current vertical place */
+/* .rt - return upward */
+
+void
+t3init(void)
+{
+	nr(L(".o"), eval(L("1i")));
+	nr(L(".o0"), eval(L("1i")));
+	nr(L(".p"), eval(L("11i")));
+	
+	addreq(L("pl"), r_warn, -1);
+	addreq(L("bp"), r_nop, -1);
+	addreq(L("pn"), r_warn, -1);
+	addreq(L("po"), r_po, -1);
+	addreq(L("ne"), r_nop, -1);
+	addreq(L("mk"), r_nop, -1);
+	addreq(L("rt"), r_warn, -1);
+}
+
blob - /dev/null
blob + eadc76edfd832889ab6f0ded5ff8154e3e17767c (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t4.c
@@ -0,0 +1,142 @@
+#include "a.h"
+
+/*
+ * 4 - Text filling, centering, and adjusting.  
+ * 	"\ " - unbreakable space
+ * 	.n register - length of last line
+ *	nl register - text baseline position on this page
+ *	.h register - baseline high water mark
+ *	.k register - current horizontal output position
+ *	\p - cause break at end of word, justify
+ *	\& - non-printing zero-width filler
+ *	tr - output translation
+ *	\c - break (but don't) input line in .nf mode
+ *	\c - break (but don't) word in .fi mode
+ */
+
+int
+e_space(void)
+{
+	return 0xA0;	/* non-breaking space */
+}
+
+int
+e_amp(void)
+{
+	return Uempty;
+}
+
+int
+e_c(void)
+{
+	getrune();
+	bol = 1;
+	return 0;
+}
+
+void
+r_br(int argc, Rune **argv)
+{
+	USED(argc);
+	USED(argv);
+	br();
+}
+
+/* fill mode on */
+void
+r_fi(int argc, Rune **argv)
+{
+	USED(argc);
+	USED(argv);
+	nr(L(".fi"), 1);
+// warn(".fi");
+}
+
+/* no-fill mode */
+void
+r_nf(int argc, Rune **argv)
+{
+	USED(argc);
+	USED(argv);
+	nr(L(".fi"), 0);
+}
+
+/* adjust */
+void
+r_ad(int argc, Rune **argv)
+{
+	int c, n;
+	
+	nr(L(".j"), getnr(L(".j"))|1);
+	if(argc < 2)
+		return;
+	c = argv[1][0];
+	switch(c){
+	default:
+		fprint(2, "%L: bad adjust %C\n", c);
+		return;
+	case 'r':
+		n = 2*2|1;
+		break;
+	case 'l':
+		n = 0;
+		break;
+	case 'c':
+		n = 1*2|1;
+		break;
+	case 'b':
+	case 'n':
+		n = 0*2|1;
+		break;
+	case '0':
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+	case '5':
+		n = c-'0';
+		break;
+	}
+	nr(L(".j"), n);
+}
+
+/* no adjust */
+void
+r_na(int argc, Rune **argv)
+{
+	USED(argc);
+	USED(argv);
+
+	nr(L(".j"), getnr(L(".j"))&~1);
+}
+
+/* center next N lines */
+void
+r_ce(int argc, Rune **argv)
+{
+	if(argc < 2)
+		nr(L(".ce"), 1);
+	else
+		nr(L(".ce"), eval(argv[1]));
+	/* XXX set trap */
+}
+
+void
+t4init(void)
+{
+	nr(L(".fi"), 1);
+	nr(L(".j"), 1);
+
+	addreq(L("br"), r_br, 0);
+	addreq(L("fi"), r_fi, 0);
+	addreq(L("nf"), r_nf, 0);
+	addreq(L("ad"), r_ad, -1);
+	addreq(L("na"), r_na, 0);
+	addreq(L("ce"), r_ce, -1);
+	
+	addesc(' ', e_space, 0);
+	addesc('p', e_warn, 0);
+	addesc('&', e_amp, 0);
+	addesc('c', e_c, 0);
+}
+
blob - /dev/null
blob + cb9519514fb2449ec11a6e740cf088278ce6db36 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t5.c
@@ -0,0 +1,110 @@
+#include "a.h"
+
+/*
+ * 5.  Vertical spacing.
+ */
+
+/* set vertical baseline spacing */
+void
+vs(int v)
+{
+	if(v == 0)
+		v = getnr(L(".v0"));
+	nr(L(".v0"), getnr(L(".v")));
+	nr(L(".v"), v);
+}
+
+void
+r_vs(int argc, Rune **argv)
+{
+	if(argc < 2)
+		vs(eval(L("12p")));
+	else if(argv[1][0] == '+')
+		vs(getnr(L(".v"))+evalscale(argv[1]+1, 'p'));
+	else if(argv[1][0] == '-')
+		vs(getnr(L(".v"))-evalscale(argv[1]+1, 'p'));
+	else
+		vs(evalscale(argv[1], 'p'));
+}
+
+/* set line spacing */
+void
+ls(int v)
+{
+	if(v == 0)
+		v = getnr(L(".ls0"));
+	nr(L(".ls0"), getnr(L(".ls")));
+	nr(L(".ls"), v);
+}
+void
+r_ls(int argc, Rune **argv)
+{
+	ls(argc < 2 ? 0 : eval(argv[1]));
+}
+
+/* .sp - space vertically */
+/* .sv - save a contiguous vertical block */
+void
+sp(int v)
+{
+	Rune buf[100];
+	double fv;
+	
+	br();
+	fv = v * 1.0/UPI;
+	if(fv > 5)
+		fv = eval(L("1v")) * 1.0/UPI;
+	runesnprint(buf, nelem(buf), "<p style=\"margin-top: 0; margin-bottom: %.2fin\"></p>\n", fv);
+	outhtml(buf);
+}
+void
+r_sp(int argc, Rune **argv)
+{
+	if(getnr(L(".ns")))
+		return;
+	if(argc < 2)
+		sp(eval(L("1v")));
+	else{
+		if(argv[1][0] == '|'){
+			/* XXX if there's no output yet, do the absolute! */
+			if(verbose)
+				warn("ignoring absolute .sp %d", eval(argv[1]+1));
+			return;
+		}
+		sp(evalscale(argv[1], 'v'));
+	}
+}
+
+void
+r_ns(int argc, Rune **argv)
+{
+	USED(argc);
+	USED(argv);
+	nr(L(".ns"), 1);
+}
+
+void
+r_rs(int argc, Rune **argv)
+{
+	USED(argc);
+	USED(argv);
+	nr(L(".ns"), 0);
+}
+
+void
+t5init(void)
+{	
+	addreq(L("vs"), r_vs, -1);
+	addreq(L("ls"), r_ls, -1);
+	addreq(L("sp"), r_sp, -1);
+	addreq(L("sv"), r_sp, -1);
+	addreq(L("os"), r_nop, -1);
+	addreq(L("ns"), r_ns, 0);
+	addreq(L("rs"), r_rs, 0);
+
+	nr(L(".v"), eval(L("12p")));
+	nr(L(".v0"), eval(L("12p")));
+	nr(L(".ls"), 1);
+	nr(L(".ls0"), 1);
+}
+
blob - /dev/null
blob + 88a069546efd85c8770d19124d8fef911e0d6c8b (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t6.c
@@ -0,0 +1,74 @@
+#include "a.h"
+
+/*
+ * Section 6 - line length and indenting.
+ */
+
+/* set line length */
+void
+ll(int v)
+{
+	if(v == 0)
+		v = getnr(L(".l0"));
+	nr(L(".l0"), getnr(L(".l")));
+	nr(L(".l"), v);
+}
+void
+r_ll(int argc, Rune **argv)
+{
+	if(argc < 2)
+		ll(0);
+	else if(argv[1][0] == '+')
+		ll(getnr(L(".l"))+evalscale(argv[1]+1, 'v'));
+	else if(argv[1][0] == '-')
+		ll(getnr(L(".l"))-evalscale(argv[1]+1, 'v'));
+	else
+		ll(evalscale(argv[1], 'm'));
+	if(argc > 2)
+		warn("extra arguments to .ll");
+}
+
+void
+in(int v)
+{
+	nr(L(".i0"), getnr(L(".i")));
+	nr(L(".i"), v);
+	/* XXX */
+}
+void
+r_in(int argc, Rune **argv)
+{
+	if(argc < 2)
+		in(getnr(L(".i0")));
+	else if(argv[1][0] == '+')
+		in(getnr(L(".i"))+evalscale(argv[1]+1, 'm'));
+	else if(argv[1][0] == '-')
+		in(getnr(L(".i"))-evalscale(argv[1]+1, 'm'));
+	else
+		in(evalscale(argv[1], 'm'));
+	if(argc > 3)
+		warn("extra arguments to .in");
+}
+
+void
+ti(int v)
+{
+	nr(L(".ti"), v);
+}
+void
+r_ti(int argc, Rune **argv)
+{
+	USED(argc);
+	ti(evalscale(argv[1], 'm'));
+}
+
+void
+t6init(void)
+{
+	addreq(L("ll"), r_ll, -1);
+	addreq(L("in"), r_in, -1);
+	addreq(L("ti"), r_ti, 1);
+	
+	nr(L(".l"), eval(L("6.5i")));
+}
+
blob - /dev/null
blob + 19b77ec1f9c3f5ac6bc7d140b1c2f83aa3a2cfe7 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t7.c
@@ -0,0 +1,543 @@
+/*
+ * 7.  Macros, strings, diversion, and position traps.
+ *
+ * 	macros can override builtins
+ *	builtins can be renamed or removed!
+ */
+
+#include "a.h"
+
+enum
+{
+	MAXARG = 10,
+	MAXMSTACK = 40
+};
+
+/* macro invocation frame */
+typedef struct Mac Mac;
+struct Mac
+{
+	int argc;
+	Rune *argv[MAXARG];
+};
+
+Mac		mstack[MAXMSTACK];
+int		nmstack;
+void		emitdi(void);
+void		flushdi(void);
+
+/*
+ * Run a user-defined macro.
+ */
+void popmacro(void);
+int
+runmacro(int dot, int argc, Rune **argv)
+{
+	Rune *p;
+	int i;
+	Mac *m;
+	
+if(verbose && isupperrune(argv[0][0])) fprint(2, "run: %S\n", argv[0]);
+	p = getds(argv[0]);
+	if(p == nil){
+		if(verbose)
+			warn("ignoring unknown request %C%S", dot, argv[0]);
+		if(verbose > 1){
+			for(i=0; i<argc; i++)
+				fprint(2, " %S", argv[i]);
+			fprint(2, "\n");
+		}
+		return -1;
+	}
+	if(nmstack >= nelem(mstack)){
+		fprint(2, "%L: macro stack overflow:");
+		for(i=0; i<nmstack; i++)
+			fprint(2, " %S", mstack[i].argv[0]);
+		fprint(2, "\n");
+		return -1;
+	}
+	m = &mstack[nmstack++];
+	m->argc = argc;
+	for(i=0; i<argc; i++)
+		m->argv[i] = erunestrdup(argv[i]);
+	pushinputstring(p);
+	nr(L(".$"), argc-1);
+	inputnotify(popmacro);
+	return 0;
+}
+
+void
+popmacro(void)
+{
+	int i;
+	Mac *m;
+	
+	if(--nmstack < 0){
+		fprint(2, "%L: macro stack underflow\n");
+		return;
+	}
+	m = &mstack[nmstack];
+	for(i=0; i<m->argc; i++)
+		free(m->argv[i]);
+	if(nmstack > 0)
+		nr(L(".$"), mstack[nmstack-1].argc-1);
+	else
+		nr(L(".$"), 0);
+}
+
+void popmacro1(void);
+jmp_buf runjb[10];
+int nrunjb;
+
+void
+runmacro1(Rune *name)
+{
+	Rune *argv[2];
+	int obol;
+	
+if(verbose) fprint(2, "outcb %p\n", outcb);
+	obol = bol;
+	argv[0] = name;
+	argv[1] = nil;
+	bol = 1;
+	if(runmacro('.', 1, argv) >= 0){
+		inputnotify(popmacro1);
+		if(!setjmp(runjb[nrunjb++]))
+			runinput();
+		else
+			if(verbose) fprint(2, "finished %S\n", name);
+	}
+	bol = obol;
+}
+
+void
+popmacro1(void)
+{
+	popmacro();
+	if(nrunjb >= 0)
+		longjmp(runjb[--nrunjb], 1);
+}
+
+/*
+ * macro arguments
+ *
+ *	"" means " inside " "
+ *	"" empty string
+ *	\newline can be done
+ *	argument separator is space (not tab)
+ *	number register .$ = number of arguments
+ *	no arguments outside macros or in strings
+ *
+ *	arguments copied in copy mode
+ */
+
+/*
+ * diversions
+ *
+ *	processed output diverted 
+ *	dn dl registers vertical and horizontal size of last diversion
+ *	.z - current diversion name
+ */
+
+/*
+ * traps
+ *
+ *	skip most
+ *	.t register - distance to next trap
+ */
+static Rune *trap0;
+
+void
+outtrap(void)
+{
+	Rune *t;
+
+	if(outcb)
+		return;
+	if(trap0){
+if(verbose) fprint(2, "trap: %S\n", trap0);
+		t = trap0;
+		trap0 = nil;
+		runmacro1(t);
+		free(t);
+	}
+}
+
+/* .wh - install trap */
+void
+r_wh(int argc, Rune **argv)
+{
+	int i;
+
+	if(argc < 2)
+		return;
+
+	i = eval(argv[1]);
+	if(argc == 2){
+		if(i == 0){
+			free(trap0);
+			trap0 = nil;
+		}else
+			if(verbose)
+				warn("not removing trap at %d", i);
+	}
+	if(argc > 2){
+		if(i == 0){
+			free(trap0);
+			trap0 = erunestrdup(argv[2]);
+		}else
+			if(verbose)
+				warn("not installing %S trap at %d", argv[2], i);
+	}
+}
+
+void
+r_ch(int argc, Rune **argv)
+{
+	int i;
+	
+	if(argc == 2){
+		if(trap0 && runestrcmp(argv[1], trap0) == 0){
+			free(trap0);
+			trap0 = nil;
+		}else
+			if(verbose)
+				warn("not removing %S trap", argv[1]);
+		return;
+	}
+	if(argc >= 3){
+		i = eval(argv[2]);
+		if(i == 0){
+			free(trap0);
+			trap0 = erunestrdup(argv[1]);
+		}else
+			if(verbose)
+				warn("not moving %S trap to %d", argv[1], i);
+	}
+}
+
+void
+r_dt(int argc, Rune **argv)
+{
+	USED(argc);
+	USED(argv);
+	warn("ignoring diversion trap");
+}
+
+/* define macro - .de, .am, .ig */
+void
+r_de(int argc, Rune **argv)
+{
+	Rune *end, *p;
+	Fmt fmt;
+	int ignore, len;
+
+	delreq(argv[1]);
+	delraw(argv[1]);
+	ignore = runestrcmp(argv[0], L("ig")) == 0;
+	if(!ignore)
+		runefmtstrinit(&fmt);
+	end = L("..");
+	if(argc >= 3)
+		end = argv[2];
+	if(runestrcmp(argv[0], L("am")) == 0 && (p=getds(argv[1])) != nil)
+		fmtrunestrcpy(&fmt, p);
+	len = runestrlen(end);
+	while((p = readline(CopyMode)) != nil){
+		if(runestrncmp(p, end, len) == 0 
+		&& (p[len]==' ' || p[len]==0 || p[len]=='\t'
+			|| (p[len]=='\\' && p[len+1]=='}'))){
+			free(p);
+			goto done;
+		}
+		if(!ignore)
+			fmtprint(&fmt, "%S\n", p);
+		free(p);
+	}
+	warn("eof in %C%S %S - looking for %#Q", dot, argv[0], argv[1], end);
+done:
+	if(ignore)
+		return;
+	p = runefmtstrflush(&fmt);
+	if(p == nil)
+		sysfatal("out of memory");
+	ds(argv[1], p);
+	free(p);
+}
+
+/* define string .ds .as */
+void
+r_ds(Rune *cmd)
+{
+	Rune *name, *line, *p;
+	
+	name = copyarg();
+	line = readline(CopyMode);
+	if(name == nil || line == nil){
+		free(name);
+		return;
+	}
+	p = line;
+	if(*p == '"')
+		p++;
+	if(cmd[0] == 'd')
+		ds(name, p);
+	else
+		as(name, p);
+	free(name);
+	free(line);
+}
+
+/* remove request, macro, or string */
+void
+r_rm(int argc, Rune **argv)
+{
+	int i;
+
+	emitdi();
+	for(i=1; i<argc; i++){
+		delreq(argv[i]);
+		delraw(argv[i]);
+		ds(argv[i], nil);
+	}
+}
+
+/* .rn - rename request, macro, or string */
+void
+r_rn(int argc, Rune **argv)
+{
+	USED(argc);
+	renreq(argv[1], argv[2]);
+	renraw(argv[1], argv[2]);
+	ds(argv[2], getds(argv[1]));
+	ds(argv[1], nil);
+}
+
+/* .di - divert output to macro xx */
+/* .da - divert, appending to macro */
+/* page offsetting is not done! */
+Fmt difmt;
+int difmtinit;
+Rune di[20][100];
+int ndi;
+
+void
+emitdi(void)
+{
+	flushdi();
+	runefmtstrinit(&difmt);
+	difmtinit = 1;
+	fmtrune(&difmt, Uformatted);
+}
+
+void
+flushdi(void)
+{
+	int n;
+	Rune *p;
+	
+	if(ndi == 0 || difmtinit == 0)
+		return;
+	fmtrune(&difmt, Uunformatted);
+	p = runefmtstrflush(&difmt);
+	memset(&difmt, 0, sizeof difmt);
+	difmtinit = 0;
+	if(p == nil)
+		warn("out of memory in diversion %C%S", dot, di[ndi-1]);
+	else{
+		n = runestrlen(p);
+		if(n > 0 && p[n-1] != '\n'){
+			p = runerealloc(p, n+2);
+			p[n] = '\n';
+			p[n+1] = 0;
+		}
+	}
+	as(di[ndi-1], p);
+	free(p);
+}
+
+void
+outdi(Rune r)
+{
+if(!difmtinit) abort();
+	if(r == Uempty)
+		return;
+	fmtrune(&difmt, r);
+}
+
+/* .di, .da */
+void
+r_di(int argc, Rune **argv)
+{
+	br();
+	if(argc > 2)
+		warn("extra arguments to %C%S", dot, argv[0]);
+	if(argc == 1){
+		/* end diversion */
+		if(ndi <= 0){
+			// warn("unmatched %C%S", dot, argv[0]);
+			return;
+		}
+		flushdi();
+		if(--ndi == 0){
+			_nr(L(".z"), nil);
+			outcb = nil;
+		}else{
+			_nr(L(".z"), di[ndi-1]);
+			runefmtstrinit(&difmt);
+			fmtrune(&difmt, Uformatted);
+			difmtinit = 1;
+		}
+		return;
+	}
+	/* start diversion */
+	/* various register state should be saved, but it's all useless to us */
+	flushdi();
+	if(ndi >= nelem(di))
+		sysfatal("%Cdi overflow", dot);
+	if(argv[0][1] == 'i')
+		ds(argv[1], nil);
+	_nr(L(".z"), argv[1]);
+	runestrcpy(di[ndi++], argv[1]);
+	runefmtstrinit(&difmt);
+	fmtrune(&difmt, Uformatted);
+	difmtinit = 1;
+	outcb = outdi;
+}
+
+/* .wh - install trap */
+/* .ch - change trap */
+/* .dt - install diversion trap */
+
+/* set input-line count trap */
+int itrapcount;
+int itrapwaiting;
+Rune *itrapname;
+
+void
+r_it(int argc, Rune **argv)
+{
+	if(argc < 3){
+		itrapcount = 0;
+		return;
+	}
+	itrapcount = eval(argv[1]);
+	free(itrapname);
+	itrapname = erunestrdup(argv[2]);
+}
+
+void
+itrap(void)
+{
+	itrapset();
+	if(itrapwaiting){
+		itrapwaiting = 0;
+		runmacro1(itrapname);
+	}
+}
+
+void
+itrapset(void)
+{
+	if(itrapcount > 0 && --itrapcount == 0)
+		itrapwaiting = 1;
+}
+
+/* .em - invoke macro when all input is over */
+void
+r_em(int argc, Rune **argv)
+{
+	Rune buf[20];
+	
+	USED(argc);
+	runesnprint(buf, nelem(buf), ".%S\n", argv[1]);
+	as(L("eof"), buf);
+}
+
+int
+e_star(void)
+{
+	Rune *p;
+	
+	p = getds(getname());
+	if(p)
+		pushinputstring(p);
+	return 0;
+}
+
+int
+e_t(void)
+{
+	if(inputmode&CopyMode)
+		return '\t';
+	return 0;
+}
+
+int
+e_a(void)
+{
+	if(inputmode&CopyMode)
+		return '\a';
+	return 0;
+}
+
+int
+e_backslash(void)
+{
+	if(inputmode&ArgMode)
+		ungetrune('\\');
+	return backslash;
+}
+
+int
+e_dot(void)
+{
+	return '.';
+}
+
+int
+e_dollar(void)
+{
+	int c;
+
+	c = getnext();
+	if(c < '1' || c > '9'){
+		ungetnext(c);
+		return 0;
+	}
+	c -= '0';
+	if(nmstack <= 0 || mstack[nmstack-1].argc <= c)
+		return 0;
+	pushinputstring(mstack[nmstack-1].argv[c]);
+	return 0;
+}
+
+void
+t7init(void)
+{	
+	addreq(L("de"), r_de, -1);
+	addreq(L("am"), r_de, -1);
+	addreq(L("ig"), r_de, -1);
+	addraw(L("ds"), r_ds);
+	addraw(L("as"), r_ds);
+	addreq(L("rm"), r_rm, -1);
+	addreq(L("rn"), r_rn, -1);
+	addreq(L("di"), r_di, -1);
+	addreq(L("da"), r_di, -1);
+	addreq(L("it"), r_it, -1);
+	addreq(L("em"), r_em, 1);
+	addreq(L("wh"), r_wh, -1);
+	addreq(L("ch"), r_ch, -1);
+	addreq(L("dt"), r_dt, -1);
+	
+	addesc('$', e_dollar, CopyMode|ArgMode|HtmlMode);
+	addesc('*', e_star, CopyMode|ArgMode|HtmlMode);
+	addesc('t', e_t, CopyMode|ArgMode);
+	addesc('a', e_a, CopyMode|ArgMode);
+	addesc('\\', e_backslash, ArgMode|CopyMode);
+	addesc('.', e_dot, CopyMode|ArgMode);
+	
+	ds(L("eof"), L(".sp 0.5i\n"));
+	ds(L(".."), L(""));
+}
+
blob - /dev/null
blob + ead5a020085382490c43bd76912c418fb7e25cc9 (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t8.c
@@ -0,0 +1,449 @@
+#include "a.h"
+/*
+ * 8. Number Registers
+ * (Reg register implementation is also here.)
+ */
+
+/*
+ *	\nx		N
+ *	\n(xx	N
+ *	\n+x		N+=M
+ *	\n-x		N-=M
+ *
+ *	.nr R ±N M
+ *	.af R c
+ *
+ *	formats
+ *		1	0, 1, 2, 3, ...
+ *		001	001, 002, 003, ...
+ *		i	0, i, ii, iii, iv, v, ...
+ *		I	0, I, II, III, IV, V, ...
+ *		a	0, a, b, ..., aa, ab, ..., zz, aaa, ...
+ *		A	0, A, B, ..., AA, AB, ..., ZZ, AAA, ...
+ *
+ *	\gx \g(xx return format of number register
+ *
+ *	.rr R
+ */
+
+typedef struct Reg Reg;
+struct Reg
+{
+	Reg *next;
+	Rune *name;
+	Rune *val;
+	Rune *fmt;
+	int inc;
+};
+
+Reg *dslist;
+Reg *nrlist;
+
+/*
+ * Define strings and numbers.
+ */
+void
+dsnr(Rune *name, Rune *val, Reg **l)
+{
+	Reg *s;
+
+	for(s = *l; s != nil; s = *l){
+		if(runestrcmp(s->name, name) == 0)
+			break;
+		l = &s->next;
+	}
+	if(val == nil){
+		if(s){
+			*l = s->next;
+			free(s->val);
+			free(s->fmt);
+			free(s);
+		}
+		return;
+	}
+	if(s == nil){
+		s = emalloc(sizeof(Reg));
+		*l = s;
+		s->name = erunestrdup(name);
+	}else
+		free(s->val);
+	s->val = erunestrdup(val);
+}
+
+Rune*
+getdsnr(Rune *name, Reg *list)
+{
+	Reg *s;
+	
+	for(s=list; s; s=s->next)
+		if(runestrcmp(name, s->name) == 0)
+			return s->val;
+	return nil;
+}
+
+void
+ds(Rune *name, Rune *val)
+{
+	dsnr(name, val, &dslist);
+}
+
+void
+as(Rune *name, Rune *val)
+{
+	Rune *p, *q;
+	
+	p = getds(name);
+	if(p == nil)
+		p = L("");
+	q = runemalloc(runestrlen(p)+runestrlen(val)+1);
+	runestrcpy(q, p);
+	runestrcat(q, val);
+	ds(name, q);
+	free(q);
+}
+
+Rune*
+getds(Rune *name)
+{
+	return getdsnr(name, dslist);
+}
+
+void
+printds(int t)
+{
+	int n, total;
+	Reg *s;
+	
+	total = 0;
+	for(s=dslist; s; s=s->next){
+		if(s->val)
+			n = runestrlen(s->val);
+		else
+			n = 0;
+		total += n;
+		if(!t)
+			fprint(2, "%S\t%d\n", s->name, n);
+	}
+	fprint(2, "total\t%d\n", total);
+}
+
+void
+nr(Rune *name, int val)
+{
+	Rune buf[20];
+	
+	runesnprint(buf, nelem(buf), "%d", val);
+	_nr(name, buf);
+}
+
+void
+af(Rune *name, Rune *fmt)
+{
+	Reg *s;
+
+	if(_getnr(name) == nil)
+		_nr(name, L("0"));
+	for(s=nrlist; s; s=s->next)
+		if(runestrcmp(s->name, name) == 0)
+			s->fmt = erunestrdup(fmt);
+}
+
+Rune*
+getaf(Rune *name)
+{
+	Reg *s;
+	
+	for(s=nrlist; s; s=s->next)
+		if(runestrcmp(s->name, name) == 0)
+			return s->fmt;
+	return nil;
+}
+
+void
+printnr(void)
+{
+	Reg *r;
+	
+	for(r=nrlist; r; r=r->next)
+		fprint(2, "%S %S %d\n", r->name, r->val, r->inc);
+}
+
+/*
+ * Some internal number registers are actually strings,
+ * so provide _ versions to get at them.
+ */
+void
+_nr(Rune *name, Rune *val)
+{
+	dsnr(name, val, &nrlist);
+}
+
+Rune*
+_getnr(Rune *name)
+{
+	return getdsnr(name, nrlist);
+}
+
+int
+getnr(Rune *name)
+{
+	Rune *p;
+
+	p = _getnr(name);
+	if(p == nil)
+		return 0;
+	return eval(p);
+}
+
+/* new register */
+void
+r_nr(int argc, Rune **argv)
+{
+	Reg *s;
+
+	if(argc < 2)
+		return;
+	if(argc < 3)
+		nr(argv[1], 0);
+	else{
+		if(argv[2][0] == '+')
+			nr(argv[1], getnr(argv[1])+eval(argv[2]+1));
+		else if(argv[2][0] == '-')
+			nr(argv[1], getnr(argv[1])-eval(argv[2]+1));
+		else
+			nr(argv[1], eval(argv[2]));
+	}
+	if(argc > 3){
+		for(s=nrlist; s; s=s->next)
+			if(runestrcmp(s->name, argv[1]) == 0)
+				s->inc = eval(argv[3]);
+	}
+}
+
+/* assign format */
+void
+r_af(int argc, Rune **argv)
+{
+	USED(argc);
+	
+	af(argv[1], argv[2]);
+}
+
+/* remove register */
+void
+r_rr(int argc, Rune **argv)
+{
+	int i;
+	
+	for(i=1; i<argc; i++)
+		_nr(argv[i], nil);
+}
+
+/* fmt integer in base 26 */
+void
+alpha(Rune *buf, int n, int a)
+{
+	int i, v;
+	
+	i = 1;
+	for(v=n; v>0; v/=26)
+		i++;
+	if(i == 0)
+		i = 1;
+	buf[i] = 0;
+	while(i > 0){
+		buf[--i] = a+n%26;
+		n /= 26;
+	}
+}
+
+struct romanv {
+	char *s;
+	int v;
+} romanv[] =
+{
+	"m",	1000,
+	"cm", 900,
+	"d", 500,
+	"cd", 400,
+	"c", 100,
+	"xc", 90,
+	"l", 50,
+	"xl", 40,
+	"x", 10,
+	"ix", 9,
+	"v", 5,
+	"iv", 4,
+	"i", 1
+};
+
+/* fmt integer in roman numerals! */
+void
+roman(Rune *buf, int n, int upper)
+{
+	Rune *p;
+	char *q;
+	struct romanv *r;
+	
+	if(upper)
+		upper = 'A' - 'a';
+	if(n >= 5000 || n <= 0){
+		runestrcpy(buf, L("-"));
+		return;
+	}
+	p = buf;
+	r = romanv;
+	while(n > 0){
+		while(n >= r->v){
+			for(q=r->s; *q; q++)
+				*p++ = *q + upper;
+			n -= r->v;
+		}
+		r++;
+	}
+	*p = 0;
+}
+
+Rune*
+getname(void)
+{
+	int i, c, cc;
+	static Rune buf[100];
+	
+	/* XXX add [name] syntax as in groff */
+	c = getnext();
+	if(c < 0)
+		return L("");
+	if(c == '\n'){
+		warn("newline in name\n");
+		ungetnext(c);
+		return L("");
+	}
+	if(c == '['){
+		for(i=0; i<nelem(buf)-1; i++){
+			if((c = getrune()) < 0)
+				return L("");
+			if(c == ']'){
+				buf[i] = 0;
+				return buf;
+			}
+			buf[i] = c;
+		}
+		return L("");
+	}
+	if(c != '('){
+		buf[0] = c;
+		buf[1] = 0;
+		return buf;
+	}
+	c = getnext();
+	cc = getnext();
+	if(c < 0 || cc < 0)
+		return L("");
+	if(c == '\n' | cc == '\n'){
+		warn("newline in \\n");
+		ungetnext(cc);
+		if(c == '\n')
+			ungetnext(c);
+	}
+	buf[0] = c;
+	buf[1] = cc;
+	buf[2] = 0;
+	return buf;
+}
+
+/* \n - return number register */
+int
+e_n(void)
+{
+	int inc, v, l;
+	Rune *name, *fmt, buf[100];
+	Reg *s;
+	
+	inc = getnext();
+	if(inc < 0)
+		return -1;
+	if(inc != '+' && inc != '-'){
+		ungetnext(inc);
+		inc = 0;
+	}
+	name = getname();
+	if(_getnr(name) == nil)
+		_nr(name, L("0"));
+	for(s=nrlist; s; s=s->next){
+		if(runestrcmp(s->name, name) == 0){
+			if(s->fmt == nil && !inc && s->val[0]){
+				/* might be a string! */
+				pushinputstring(s->val);
+				return 0;
+			}
+			v = eval(s->val);
+			if(inc){
+				if(inc == '+')
+					v += s->inc;
+				else
+					v -= s->inc;
+				runesnprint(buf, nelem(buf), "%d", v);
+				free(s->val);
+				s->val = erunestrdup(buf);
+			}
+			fmt = s->fmt;
+			if(fmt == nil)
+				fmt = L("1");
+			switch(fmt[0]){
+			case 'i':
+			case 'I':
+				roman(buf, v, fmt[0]=='I');
+				break;
+			case 'a':
+			case 'A':
+				alpha(buf, v, fmt[0]);
+				break;
+			default:
+				l = runestrlen(fmt);
+				if(l == 0)
+					l = 1;
+				runesnprint(buf, sizeof buf, "%0*d", l, v);
+				break;
+			}
+			pushinputstring(buf);
+			return 0;
+		}
+	}
+	pushinputstring(L(""));
+	return 0;
+}
+
+/* \g - number register format */
+int
+e_g(void)
+{
+	Rune *p;
+
+	p = getaf(getname());
+	if(p == nil)
+		p = L("1");
+	pushinputstring(p);
+	return 0;
+}
+
+void
+r_pnr(int argc, Rune **argv)
+{
+	USED(argc);
+	USED(argv);
+	printnr();
+}
+
+void
+t8init(void)
+{
+	addreq(L("nr"), r_nr, -1);
+	addreq(L("af"), r_af, 2);
+	addreq(L("rr"), r_rr, -1);
+	addreq(L("pnr"), r_pnr, 0);
+	
+	addesc('n', e_n, CopyMode|ArgMode|HtmlMode);
+	addesc('g', e_g, 0);
+}
+
blob - /dev/null
blob + c9e045642501c87185bd0340dd1c7c9f803428aa (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/t9.c
@@ -0,0 +1,6 @@
+/*
+ * 9.  Tabs, leaders, and fields.
+ */
+
+XXX
+
blob - /dev/null
blob + 99e99543f16d229d322dfe99f2f7d194769ec68a (mode 644)
--- /dev/null
+++ src/cmd/htmlroff/util.c
@@ -0,0 +1,123 @@
+#include "a.h"
+
+void*
+emalloc(uint n)
+{
+	void *v;
+	
+	v = mallocz(n, 1);
+	if(v == nil)
+		sysfatal("out of memory");
+	return v;
+}
+
+char*
+estrdup(char *s)
+{
+	char *t;
+	
+	t = strdup(s);
+	if(t == nil)
+		sysfatal("out of memory");
+	return t;
+}
+
+Rune*
+erunestrdup(Rune *s)
+{
+	Rune *t;
+
+	t = emalloc(sizeof(Rune)*(runestrlen(s)+1));
+	if(t == nil)
+		sysfatal("out of memory");
+	runestrcpy(t, s);
+	return t;
+}
+
+void*
+erealloc(void *ov, uint n)
+{
+	void *v;
+	
+	v = realloc(ov, n);
+	if(v == nil)
+		sysfatal("out of memory");
+	return v;
+}
+
+Rune*
+erunesmprint(char *fmt, ...)
+{
+	Rune *s;
+	va_list arg;
+	
+	va_start(arg, fmt);
+	s = runevsmprint(fmt, arg);
+	va_end(arg);
+	if(s == nil)
+		sysfatal("out of memory");
+	return s;
+}
+
+char*
+esmprint(char *fmt, ...)
+{
+	char *s;
+	va_list arg;
+	
+	va_start(arg, fmt);
+	s = vsmprint(fmt, arg);
+	va_end(arg);
+	if(s == nil)
+		sysfatal("out of memory");
+	return s;
+}
+
+void
+warn(char *fmt, ...)
+{
+	va_list arg;
+	
+	fprint(2, "htmlroff: %L: ");
+	va_start(arg, fmt);
+	vfprint(2, fmt, arg);
+	va_end(arg);
+	fprint(2, "\n");
+}
+
+/*
+ * For non-Unicode compilers, so we can say
+ * L("asdf") and get a Rune string.  Assumes strings
+ * are identified by their pointers, so no mutable strings!
+ */
+typedef struct Lhash Lhash;
+struct Lhash
+{
+	char *s;
+	Lhash *next;
+	Rune r[1];
+};
+static Lhash *hash[1127];
+
+Rune*
+L(char *s)
+{
+	Rune *p;
+	Lhash *l;
+	uint h;
+
+	h = (uintptr)s%nelem(hash);
+	for(l=hash[h]; l; l=l->next)
+		if(l->s == s)
+			return l->r;
+	l = emalloc(sizeof *l+(utflen(s)+1)*sizeof(Rune));
+	p = l->r;
+	l->s = s;
+	while(*s)
+		s += chartorune(p++, s);
+	*p = 0;
+	l->next = hash[h];
+	hash[h] = l;
+	return l->r;
+}
+
blob - /dev/null
blob + d03381e769fa4241d98fa640e0e928efc09e6c7e (mode 644)
--- /dev/null
+++ tmac/tmac.html
@@ -0,0 +1,94 @@
+.de HTML
+\! \<?xml version="1.0" encoding="utf-8"?\>
+\! \<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+\!   "http://www.w3.org/TR/html4/loose.dtd"\>
+.html html <html>
+.html head <head>
+.if !'\\$1'' .html title <title>\\$1</title>
+.HEAD
+.html head
+.html body <body>
+..
+.de FSFIRST
+.de NOTES xx
+._NOTES
+.rm _NOTES
+xx
+.em NOTES
+.da _NOTES
+.sp
+.B "Notes
+.sp
+.da
+..
+.de FS
+.FSFIRST
+.rm FSFIRST
+.da _NOTES
+..
+.de FE
+.sp
+.da
+..
+.nr png -1 1
+.de TS
+.ds pngbase "\\*[basename]
+.if '\\*[pngbase]'' .ds pngbase \\n(.B
+.ds pngfile \\*[pngbase]\\n+[png].png
+.html - <center><img src="\\*[pngfile]"></center>
+.\" The .inputpipe must be the last line of the macro!
+.inputpipe .TE troff2png >\\*[pngfile]
+..
+.de TE
+..
+.de PS
+.ds pngbase "\\*[basename]
+.if '\\*[pngbase]'' .ds pngbase \\n(.B
+.ds pngfile \\*[pngbase]\\n+[png].png
+.html - <center><img src="\\*[pngfile]"></center>
+.inputpipe .PE troff2png >\\*[pngfile]
+..
+.de PE
+..
+.de B1
+.margin 0
+.nr TW 10
+.nr TW1 80
+.if !'\\$1'' .nr TW \\$1
+.if !'\\$2'' .nr TW1 \\$2
+.html box \
+<center>\
+<table width=\\n[TW1]% cellspacing=0 cellpadding=0 border=0>\
+<tr height=1>\
+	<td width=1 bgcolor=#000000 />\
+	<td width=\\n(TW bgcolor=#000000 />\
+	<td bgcolor=#000000 />\
+	<td width=\\n(TW bgcolor=#000000 />\
+	<td width=1 bgcolor=#000000 />\
+</tr>\
+<tr height=\\n(TW>\
+	<td width=1 bgcolor=#000000 />\
+	<td width=\\n(TW />\
+	<td />\
+	<td width=\\n(TW />\
+	<td width=1 bgcolor=#000000 />\
+</tr>
+.html box0 <tr>
+.html box1 <td width=1 bgcolor=#000000 /><td width=\\n(TW /><td>
+..
+.de B2
+.html box1 <td width=\\n(TW /><td width=1 bgcolor=#000000 />
+.html box0 <tr height=\\n(TW><td width=1 bgcolor=#000000 />\
+	<td width=\\n(TW /><td /><td width=\\n(TW />\
+	<td width=1 bgcolor=#000000 />\
+</tr>\
+<tr height=1>\
+	<td width=1 bgcolor=#000000 />\
+	<td width=\\n(TW bgcolor=#000000 />\
+	<td bgcolor=#000000 />\
+	<td width=\\n(TW bgcolor=#000000 />\
+	<td width=1 bgcolor=#000000 />\
+</tr>
+.html box
+.margin 1
+..
blob - 0183e2d9281a76c73befe7f955ab170b3eb9bd75
blob + c14de15dc02c8b9ff2260e33ce3bfb7b577ec7c3
--- tmac/tmac.s
+++ tmac/tmac.s
@@ -308,9 +308,11 @@
 .di WT
 .na
 .fi
+.ie h .ll \\n(LLu
+.el \{\
 .ll 5.0i
 .if n .if \\n(TN .ll 29
-.if t .if \\n(TN .ll 3.5i
+.if t .if \\n(TN .ll 3.5i \}
 .ft 3
 .ps \\n(PS
 .if !\\n(TN \{\
@@ -318,6 +320,7 @@
 .	vs \\n(.s+2
 .	rm CS\}
 .hy 0
+.if h .ce 999
 ..
 .de TX
 .rs
@@ -358,6 +361,7 @@
 .		ft 3
 .		ll 16\}\}
 .ps \\n(PS
+.if h .ce 999
 ..
 .de AX
 .ft 1
@@ -503,6 +507,7 @@ ABSTRACT
 .ie \\n(VS>=41 .vs \\n(VSu
 .el .vs \\n(VSp
 .ti +\\n(PIu
+.fi
 ..
 .	\"AE - end of an abstract
 .de AE
@@ -704,6 +709,14 @@ Computing Science Technical Report No. \\*(MN
 .if \\$1H .TQ
 .nr IX 1
 ..
+.if h \{\
+.de TS
+.nr tp -1 1
+.ds tp x\\n+(tp.png
+.html - <center><img src="\\*(tp"></center>
+.dp .TE troff2png >\\*(tp
+..
+.\}
 .de TQ
 .di TT
 .nr IT 1
@@ -818,9 +831,18 @@ Computing Science Technical Report No. \\*(MN
 .in
 .if \\n($1>0 .sp .65
 ..
+.if h \{\
+.de PS
+.nr tp -1 1
+.ds tp x\\n+(tp.png
+.html - <center><img src="\\*(tp" /></center>
+.dp .PE troff2png >\\*(tp
+..
+.\}
 .			\" .P1/.P2 macros for programs
 .
 .nr XP 1	\" delta point size for program
+.if h .nr XP 0
 .nr XV 1p	\" delta vertical for programs
 .nr XT 8	\" delta tab stop for programs
 .nr DV .5v	\" space before start of program
@@ -832,10 +854,11 @@ Computing Science Technical Report No. \\*(MN
 .br
 .nr v \\n(.v
 .di p1
-.in \\n(P1u
+.in +\\n(P1u
 .nf
 .ps -\\n(XP
 .vs -\\n(XVu
+.nr xx \\n(.sp
 .ft CW
 .nr t \\n(XT*\\w'x'u
 .ta 1u*\\ntu 2u*\\ntu 3u*\\ntu 4u*\\ntu 5u*\\ntu 6u*\\ntu 7u*\\ntu 8u*\\ntu 9u*\\ntu 10u*\\ntu 11u*\\ntu 12u*\\ntu 13u*\\ntu 14u*\\ntu
@@ -1565,6 +1588,50 @@ operating system\\$1
 .if \\n(BQ .fi
 .br
 ..
+.if h \{\
+.de B1
+.margin 0
+.nr TW 10
+.nr TW1 80
+.if !'\\$1'' .nr TW \\$1
+.if !'\\$2'' .nr TW1 \\$2
+.html pic \
+<center>\
+<table width=\\n[TW1]% cellspacing=0 cellpadding=0 border=0>\
+<tr height=1>\
+	<td width=1 bgcolor=#000000 />\
+	<td width=\\n(TW bgcolor=#000000 />\
+	<td bgcolor=#000000 />\
+	<td width=\\n(TW bgcolor=#000000 />\
+	<td width=1 bgcolor=#000000 />\
+</tr>\
+<tr height=\\n(TW>\
+	<td width=1 bgcolor=#000000 />\
+	<td width=\\n(TW />\
+	<td />\
+	<td width=\\n(TW />\
+	<td width=1 bgcolor=#000000 />\
+</tr>
+.html	pic0 <tr>
+.html pic1 <td width=1 bgcolor=#000000 /><td width=\\n(TW /><td>\}
+..
+.de B2
+.html pic1 <td width=\\n(TW /><td width=1 bgcolor=#000000 />
+.html pic0 <tr height=\\n(TW><td width=1 bgcolor=#000000 />\
+	<td width=\\n(TW /><td /><td width=\\n(TW />\
+	<td width=1 bgcolor=#000000 />\
+</tr>\
+<tr height=1>\
+	<td width=1 bgcolor=#000000 />\
+	<td width=\\n(TW bgcolor=#000000 />\
+	<td bgcolor=#000000 />\
+	<td width=\\n(TW bgcolor=#000000 />\
+	<td width=1 bgcolor=#000000 />\
+</tr>
+.html pic \}
+.margin 1
+..
+.\}
 .de AT
 .nf
 .sp
blob - 40c712dca865398d66d5fa452eb358e254ee30dd
blob + 63311c640f8c9d5eff7d3538bacb6454eb659d1a
--- tmac/tmac.skeep
+++ tmac/tmac.skeep
@@ -71,12 +71,14 @@
 .in 0
 .ls 1
 .if \\n(TB=0 .ev
+.if \\n(TB=0 .KX
 .if \\n(TB=0 .br
 .if \\n(TB=0 .ev 2
 .if \\n(TB=0 .KK
 .ls
 .ce 0
 .if \\n(TB=0 .rm KK
+.if \\n(TB=0 .KY
 .if \\n(TB .da KJ
 .if \\n(TB \!.KD \\n(dn \\n(KV
 .if \\n(TB .KK