tadd - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit c42a1d3d6168df56f966ea1f3ba3ef39ebbff4e4
 (DIR) parent 49a1496cbbb871bc623cfd0925566628e246c9ba
 (HTM) Author: rsc <devnull@localhost>
       Date:   Tue, 21 Feb 2006 18:37:05 +0000
       
       add
       
       Diffstat:
         A src/cmd/htmlroff/a.h                |     148 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/char.c             |     116 ++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/html.c             |     287 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/input.c            |     241 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/main.c             |      72 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/mkfile             |      58 ++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/roff.c             |     750 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t1.c               |     186 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t10.c              |     140 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t11.c              |     107 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t12.c              |      67 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t13.c              |      17 +++++++++++++++++
         A src/cmd/htmlroff/t14.c              |      33 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t15.c              |      13 +++++++++++++
         A src/cmd/htmlroff/t16.c              |     156 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t17.c              |     131 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t18.c              |      67 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t19.c              |     142 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t2.c               |     274 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t20.c              |      79 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t3.c               |      49 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t4.c               |     142 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t5.c               |     110 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t6.c               |      74 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t7.c               |     543 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t8.c               |     449 +++++++++++++++++++++++++++++++
         A src/cmd/htmlroff/t9.c               |       6 ++++++
         A src/cmd/htmlroff/util.c             |     123 +++++++++++++++++++++++++++++++
         A tmac/tmac.html                      |      94 +++++++++++++++++++++++++++++++
         M tmac/tmac.s                         |      71 ++++++++++++++++++++++++++++++-
         M tmac/tmac.skeep                     |       2 ++
       
       31 files changed, 4745 insertions(+), 2 deletions(-)
       ---
 (DIR) diff --git a/src/cmd/htmlroff/a.h b/src/cmd/htmlroff/a.h
       t@@ -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
 (DIR) diff --git a/src/cmd/htmlroff/char.c b/src/cmd/htmlroff/char.c
       t@@ -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;
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/html.c b/src/cmd/htmlroff/html.c
       t@@ -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));
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/input.c b/src/cmd/htmlroff/input.c
       t@@ -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;
       +        }
       +}
 (DIR) diff --git a/src/cmd/htmlroff/main.c b/src/cmd/htmlroff/main.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/mkfile b/src/cmd/htmlroff/mkfile
       t@@ -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
       +
 (DIR) diff --git a/src/cmd/htmlroff/roff.c b/src/cmd/htmlroff/roff.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/htmlroff/t1.c b/src/cmd/htmlroff/t1.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t10.c b/src/cmd/htmlroff/t10.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t11.c b/src/cmd/htmlroff/t11.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t12.c b/src/cmd/htmlroff/t12.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/htmlroff/t13.c b/src/cmd/htmlroff/t13.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t14.c b/src/cmd/htmlroff/t14.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t15.c b/src/cmd/htmlroff/t15.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t16.c b/src/cmd/htmlroff/t16.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/htmlroff/t17.c b/src/cmd/htmlroff/t17.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/htmlroff/t18.c b/src/cmd/htmlroff/t18.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/htmlroff/t19.c b/src/cmd/htmlroff/t19.c
       t@@ -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());
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t2.c b/src/cmd/htmlroff/t2.c
       t@@ -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 */
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t20.c b/src/cmd/htmlroff/t20.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t3.c b/src/cmd/htmlroff/t3.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t4.c b/src/cmd/htmlroff/t4.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t5.c b/src/cmd/htmlroff/t5.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t6.c b/src/cmd/htmlroff/t6.c
       t@@ -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")));
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t7.c b/src/cmd/htmlroff/t7.c
       t@@ -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(""));
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t8.c b/src/cmd/htmlroff/t8.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/htmlroff/t9.c b/src/cmd/htmlroff/t9.c
       t@@ -0,0 +1,6 @@
       +/*
       + * 9.  Tabs, leaders, and fields.
       + */
       +
       +XXX
       +
 (DIR) diff --git a/src/cmd/htmlroff/util.c b/src/cmd/htmlroff/util.c
       t@@ -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;
       +}
       +
 (DIR) diff --git a/tmac/tmac.html b/tmac/tmac.html
       t@@ -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
       +..
 (DIR) diff --git a/tmac/tmac.s b/tmac/tmac.s
       t@@ -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 \{\
       t@@ -318,6 +320,7 @@
        .        vs \\n(.s+2
        .        rm CS\}
        .hy 0
       +.if h .ce 999
        ..
        .de TX
        .rs
       t@@ -358,6 +361,7 @@
        .                ft 3
        .                ll 16\}\}
        .ps \\n(PS
       +.if h .ce 999
        ..
        .de AX
        .ft 1
       t@@ -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
       t@@ -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
       t@@ -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
       t@@ -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
       t@@ -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
 (DIR) diff --git a/tmac/tmac.skeep b/tmac/tmac.skeep
       t@@ -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