aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2006-02-21 18:37:05 +0000
committerrsc <devnull@localhost>2006-02-21 18:37:05 +0000
commitc42a1d3d6168df56f966ea1f3ba3ef39ebbff4e4 (patch)
tree400f263e56681842ba1e6e1fdd8be453856474ef
parent49a1496cbbb871bc623cfd0925566628e246c9ba (diff)
downloadplan9port-c42a1d3d6168df56f966ea1f3ba3ef39ebbff4e4.tar.gz
plan9port-c42a1d3d6168df56f966ea1f3ba3ef39ebbff4e4.tar.bz2
plan9port-c42a1d3d6168df56f966ea1f3ba3ef39ebbff4e4.zip
add
-rw-r--r--src/cmd/htmlroff/a.h148
-rw-r--r--src/cmd/htmlroff/char.c116
-rw-r--r--src/cmd/htmlroff/html.c287
-rw-r--r--src/cmd/htmlroff/input.c241
-rw-r--r--src/cmd/htmlroff/main.c72
-rw-r--r--src/cmd/htmlroff/mkfile58
-rw-r--r--src/cmd/htmlroff/roff.c750
-rw-r--r--src/cmd/htmlroff/t1.c186
-rw-r--r--src/cmd/htmlroff/t10.c140
-rw-r--r--src/cmd/htmlroff/t11.c107
-rw-r--r--src/cmd/htmlroff/t12.c67
-rw-r--r--src/cmd/htmlroff/t13.c17
-rw-r--r--src/cmd/htmlroff/t14.c33
-rw-r--r--src/cmd/htmlroff/t15.c13
-rw-r--r--src/cmd/htmlroff/t16.c156
-rw-r--r--src/cmd/htmlroff/t17.c131
-rw-r--r--src/cmd/htmlroff/t18.c67
-rw-r--r--src/cmd/htmlroff/t19.c142
-rw-r--r--src/cmd/htmlroff/t2.c274
-rw-r--r--src/cmd/htmlroff/t20.c79
-rw-r--r--src/cmd/htmlroff/t3.c49
-rw-r--r--src/cmd/htmlroff/t4.c142
-rw-r--r--src/cmd/htmlroff/t5.c110
-rw-r--r--src/cmd/htmlroff/t6.c74
-rw-r--r--src/cmd/htmlroff/t7.c543
-rw-r--r--src/cmd/htmlroff/t8.c449
-rw-r--r--src/cmd/htmlroff/t9.c6
-rw-r--r--src/cmd/htmlroff/util.c123
-rw-r--r--tmac/tmac.html94
-rw-r--r--tmac/tmac.s71
-rw-r--r--tmac/tmac.skeep2
31 files changed, 4745 insertions, 2 deletions
diff --git a/src/cmd/htmlroff/a.h b/src/cmd/htmlroff/a.h
new file mode 100644
index 00000000..c17da850
--- /dev/null
+++ b/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
diff --git a/src/cmd/htmlroff/char.c b/src/cmd/htmlroff/char.c
new file mode 100644
index 00000000..1c7d1237
--- /dev/null
+++ b/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;
+}
+
diff --git a/src/cmd/htmlroff/html.c b/src/cmd/htmlroff/html.c
new file mode 100644
index 00000000..fd48382a
--- /dev/null
+++ b/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));
+}
+
diff --git a/src/cmd/htmlroff/input.c b/src/cmd/htmlroff/input.c
new file mode 100644
index 00000000..99e0d56e
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/src/cmd/htmlroff/main.c b/src/cmd/htmlroff/main.c
new file mode 100644
index 00000000..b6af1e7b
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/htmlroff/mkfile b/src/cmd/htmlroff/mkfile
new file mode 100644
index 00000000..641bf803
--- /dev/null
+++ b/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
+
diff --git a/src/cmd/htmlroff/roff.c b/src/cmd/htmlroff/roff.c
new file mode 100644
index 00000000..6a7cd09e
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/htmlroff/t1.c b/src/cmd/htmlroff/t1.c
new file mode 100644
index 00000000..8236694d
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/htmlroff/t10.c b/src/cmd/htmlroff/t10.c
new file mode 100644
index 00000000..e029db35
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/htmlroff/t11.c b/src/cmd/htmlroff/t11.c
new file mode 100644
index 00000000..53d68aac
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/htmlroff/t12.c b/src/cmd/htmlroff/t12.c
new file mode 100644
index 00000000..5ec577d3
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/htmlroff/t13.c b/src/cmd/htmlroff/t13.c
new file mode 100644
index 00000000..0fadab3a
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/htmlroff/t14.c b/src/cmd/htmlroff/t14.c
new file mode 100644
index 00000000..1dab3516
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/htmlroff/t15.c b/src/cmd/htmlroff/t15.c
new file mode 100644
index 00000000..fbfd5128
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/htmlroff/t16.c b/src/cmd/htmlroff/t16.c
new file mode 100644
index 00000000..3a9c427e
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/htmlroff/t17.c b/src/cmd/htmlroff/t17.c
new file mode 100644
index 00000000..2800ec76
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/htmlroff/t18.c b/src/cmd/htmlroff/t18.c
new file mode 100644
index 00000000..f5c74a1f
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/htmlroff/t19.c b/src/cmd/htmlroff/t19.c
new file mode 100644
index 00000000..a4cc18f4
--- /dev/null
+++ b/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());
+}
+
diff --git a/src/cmd/htmlroff/t2.c b/src/cmd/htmlroff/t2.c
new file mode 100644
index 00000000..54481d0a
--- /dev/null
+++ b/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 */
+}
+
diff --git a/src/cmd/htmlroff/t20.c b/src/cmd/htmlroff/t20.c
new file mode 100644
index 00000000..62ea914f
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/htmlroff/t3.c b/src/cmd/htmlroff/t3.c
new file mode 100644
index 00000000..e54573c1
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/htmlroff/t4.c b/src/cmd/htmlroff/t4.c
new file mode 100644
index 00000000..eadc76ed
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/htmlroff/t5.c b/src/cmd/htmlroff/t5.c
new file mode 100644
index 00000000..cb951951
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/htmlroff/t6.c b/src/cmd/htmlroff/t6.c
new file mode 100644
index 00000000..88a06954
--- /dev/null
+++ b/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")));
+}
+
diff --git a/src/cmd/htmlroff/t7.c b/src/cmd/htmlroff/t7.c
new file mode 100644
index 00000000..19b77ec1
--- /dev/null
+++ b/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(""));
+}
+
diff --git a/src/cmd/htmlroff/t8.c b/src/cmd/htmlroff/t8.c
new file mode 100644
index 00000000..ead5a020
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/htmlroff/t9.c b/src/cmd/htmlroff/t9.c
new file mode 100644
index 00000000..c9e04564
--- /dev/null
+++ b/src/cmd/htmlroff/t9.c
@@ -0,0 +1,6 @@
+/*
+ * 9. Tabs, leaders, and fields.
+ */
+
+XXX
+
diff --git a/src/cmd/htmlroff/util.c b/src/cmd/htmlroff/util.c
new file mode 100644
index 00000000..99e99543
--- /dev/null
+++ b/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;
+}
+
diff --git a/tmac/tmac.html b/tmac/tmac.html
new file mode 100644
index 00000000..d03381e7
--- /dev/null
+++ b/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
+..
diff --git a/tmac/tmac.s b/tmac/tmac.s
index 0183e2d9..c14de15d 100644
--- a/tmac/tmac.s
+++ b/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
diff --git a/tmac/tmac.skeep b/tmac/tmac.skeep
index 40c712dc..63311c64 100644
--- a/tmac/tmac.skeep
+++ b/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