tvarious little bug fixes - plan9port - [fork] Plan 9 from user space
git clone git://src.adamsgaard.dk/plan9port
Log
Files
Refs
README
LICENSE
---
commit 892de7987409ccd209dd4f43fb7717408d4a04bd
parent 6b53e2a4d0d41305efa4b8b0800d119a13478ded
Author: rsc 
Date:   Mon, 19 Apr 2004 05:54:21 +0000

various little bug fixes

Diffstat:
  M src/cmd/acme/dat.h                  |       3 +++
  M src/cmd/acme/exec.c                 |      11 +++++++++++
  M src/cmd/bzip2/bzip2.c               |      16 ++++++++++++++++
  A src/cmd/fmt.c                       |     241 +++++++++++++++++++++++++++++++
  M src/cmd/gzip/gzip.c                 |      16 ++++++++++++++++
  M src/cmd/mkfile                      |       4 ++--
  M src/cmd/rio/menu.c                  |      10 ++++------
  A src/cmd/tweak.c                     |    2058 +++++++++++++++++++++++++++++++

8 files changed, 2351 insertions(+), 8 deletions(-)
---
diff --git a/src/cmd/acme/dat.h b/src/cmd/acme/dat.h
t@@ -174,6 +174,9 @@ struct Text
         uint        org;
         uint        q0;
         uint        q1;
+        uint        oldorg;
+        uint        oldq0;
+        uint        oldq1;
         int        what;
         int        tabstop;
         Window        *w;
diff --git a/src/cmd/acme/exec.c b/src/cmd/acme/exec.c
t@@ -580,6 +580,9 @@ get(Text *et, Text *t, Text *argt, int flag1, int _0, Rune *arg, int narg)
         r = bytetorune(name, &n);
         for(i=0; ifile->ntext; i++){
                 u = t->file->text[i];
+                u->oldorg = u->org;
+                u->oldq0 = u->q0;
+                u->oldq1 = u->q1;
                 /* second and subsequent calls with zero an already empty buffer, but OK */
                 textreset(u);
                 windirfree(u->w);
t@@ -601,6 +604,14 @@ get(Text *et, Text *t, Text *argt, int flag1, int _0, Rune *arg, int narg)
         t->file->unread = FALSE;
         for(i=0; ifile->ntext; i++){
                 u = t->file->text[i];
+                if(u->oldorg > u->file->b.nc)
+                        u->oldorg = u->file->b.nc;
+                if(u->oldq0 > u->file->b.nc)
+                        u->oldq0 = u->file->b.nc;
+                if(u->oldq1 > u->file->b.nc)
+                        u->oldq1 = u->file->b.nc;
+                u->org = u->oldorg;
+                textshow(u, u->oldq0, u->oldq1, 1);
                 textsetselect(&u->w->tag, u->w->tag.file->b.nc, u->w->tag.file->b.nc);
                 textscrdraw(u);
         }
diff --git a/src/cmd/bzip2/bzip2.c b/src/cmd/bzip2/bzip2.c
t@@ -23,7 +23,9 @@ void
 main(int argc, char **argv)
 {
         int i, ok, stdout;
+        char **oargv;
 
+        oargv = argv;
         level = 6;
         stdout = 0;
         ARGBEGIN{
t@@ -36,6 +38,20 @@ main(int argc, char **argv)
         case 'c':
                 stdout++;
                 break;
+        case 'd':
+                /*
+                 * gnu tar expects bzip2 -d to decompress
+                 * humor it.  ugh.
+                 */
+                /* remove -d from command line - magic! */
+                if(strcmp(argv[0], "-d") == 0){
+                        while(*argv++)
+                                *(argv-1) = *argv;
+                }else
+                        memmove(_args-1, _args, strlen(_args)+1);
+                exec("bunzip2", oargv);
+                sysfatal("exec bunzip2 failed");
+                break;
         case '1': case '2': case '3': case '4':
         case '5': case '6': case '7': case '8': case '9':
                 level = ARGC() - '0';
diff --git a/src/cmd/fmt.c b/src/cmd/fmt.c
t@@ -0,0 +1,241 @@
+#include 
+#include 
+#include 
+#include 
+
+/*
+ * block up paragraphs, possibly with indentation
+ */
+
+int extraindent = 0;                /* how many spaces to indent all lines */
+int indent = 0;                        /* current value of indent, before extra indent */
+int length = 70;                /* how many columns per output line */
+int join = 1;                        /* can lines be joined? */
+int maxtab = 8;
+Biobuf bin;
+Biobuf bout;
+
+typedef struct Word Word;
+struct Word{
+        int        bol;
+        int        indent;
+        char        text[1];
+};
+
+void        fmt(void);
+
+void
+usage(void)
+{
+        fprint(2, "usage: %s [-j] [-i indent] [-l length] [file...]\n", argv0);
+        exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+        int i, f;
+        char *s, *err;
+
+        ARGBEGIN{
+        case 'i':
+                extraindent = atoi(EARGF(usage()));
+                break;
+        case 'j':
+                join = 0;
+                break;
+        case 'w':
+        case 'l':
+                length = atoi(EARGF(usage()));
+                break;
+        default:
+                usage();
+        }ARGEND
+
+        if(length <= indent){
+                fprint(2, "%s: line length<=indentation\n", argv0);
+                exits("length");
+        }
+
+        s=getenv("tabstop");
+        if(s!=nil && atoi(s)>0)
+                maxtab=atoi(s);
+        err = nil;
+        Binit(&bout, 1, OWRITE);
+        if(argc <= 0){
+                Binit(&bin, 0, OREAD);
+                fmt();
+        }else{
+                for(i=0; itext, s, l);
+        w->text[l] = '\0';
+        w->indent = indent;
+        w->bol = bol;
+        words = realloc(words, (*nwordp+1)*sizeof(Word*));
+        words[(*nwordp)++] = w;
+        return words;
+}
+
+Word**
+parseline(char *line, Word **words, int *nwordp)
+{
+        int ind, l, bol;
+
+        ind = indentof(&line);
+        indent = ind;
+        bol = 1;
+        for(;;){
+                /* find next word */
+                while(*line==' ' || *line=='\t')
+                        line++;
+                if(*line == '\0'){
+                        if(bol)
+                                return addword(words, nwordp, "", 0, -1, bol);
+                        break;
+                }
+                /* how long is this word? */
+                for(l=0; line[l]; l++)
+                        if(line[l]==' ' || line[l]=='\t')
+                                break;
+                words = addword(words, nwordp, line, l, indent, bol);
+                bol = 0;
+                line += l;
+        }
+        return words;
+}
+
+void
+printindent(int w)
+{
+        while(w >= maxtab){
+                Bputc(&bout, '\t');
+                w -= maxtab;
+        }
+        while(w > 0){
+                Bputc(&bout, ' ');
+                w--;
+        }
+}
+
+/* give extra space if word ends with period, etc. */
+int
+nspaceafter(char *s)
+{
+        int n;
+
+        n = strlen(s);
+        if(n < 2)
+                return 1;
+        if(isupper(s[0]) && n < 4)
+                return 1;
+        if(strchr(".!?", s[n-1]) != nil)
+                return 2;
+        return 1;
+}
+        
+
+void
+printwords(Word **w, int nw)
+{
+        int i, j, n, col, nsp;
+
+        /* one output line per loop */
+        for(i=0; iindent == -1){
+                        Bputc(&bout, '\n');
+                        if(++i == nw)        /* out of words */
+                                break;
+                }
+                /* emit leading indent */
+                col = extraindent+w[i]->indent;
+                printindent(col);
+                /* emit words until overflow; always emit at least one word */
+                for(n=0;; n++){
+                        Bprint(&bout, "%s", w[i]->text);
+                        col += utflen(w[i]->text);
+                        if(++i == nw)
+                                break;        /* out of words */
+                        if(w[i]->indent != w[i-1]->indent)
+                                break;        /* indent change */
+                        nsp = nspaceafter(w[i-1]->text);
+                        if(col+nsp+utflen(w[i]->text) > extraindent+length)
+                                break;        /* fold line */
+                        if(!join && n != 0 && w[i]->bol)
+                                break;
+                        for(j=0; j
diff --git a/src/cmd/gzip/gzip.c b/src/cmd/gzip/gzip.c
t@@ -29,13 +29,29 @@ void
 main(int argc, char *argv[])
 {
         int i, ok, stdout;
+        char **oargv;
 
+        oargv = argv;
         level = 6;
         stdout = 0;
         ARGBEGIN{
         case 'D':
                 debug++;
                 break;
+        case 'd':
+                /*
+                 * gnu tar expects gzip -d to decompress
+                 * humor it.  ugh.
+                 */
+                /* remove -d from command line - magic! */
+                if(strcmp(argv[0], "-d") == 0){
+                        while(*argv++)
+                                *(argv-1) = *argv;
+                }else
+                        memmove(_args-1, _args, strlen(_args)+1);
+                exec("gunzip", oargv);
+                sysfatal("exec gunzip failed");
+                break;
         case 'v':
                 verbose++;
                 break;
diff --git a/src/cmd/mkfile b/src/cmd/mkfile
t@@ -2,8 +2,8 @@ PLAN9=../..
 <$PLAN9/src/mkhdr
 
 TARG=`ls *.c | sed 's/\.c//'`
-LDFLAGS=$LDFLAGS
-SHORTLIB=sec fs mux regexp9 thread bio 9
+LDFLAGS=$LDFLAGS -L$X11/lib -lX11
+SHORTLIB=sec fs mux regexp9 draw thread bio 9
 
 <$PLAN9/src/mkmany
 
diff --git a/src/cmd/rio/menu.c b/src/cmd/rio/menu.c
t@@ -47,11 +47,10 @@ button(XButtonEvent *e)
         if (s == 0)
                 return;
         c = getclient(e->window, 0);
-        if (c) {
+        if(c){
                 if (debug) fprintf(stderr, "but: e x=%d y=%d c x=%d y=%d dx=%d dy=%d BORDR %d\n",
                                 e->x, e->y, c->x, c->y, c->dx, c->dy, BORDER);
-            if (e->x <= BORDER || e->x > (c->dx + BORDER) ||
-                e->y <= BORDER || e->y > (c->dy + BORDER)) {
+                if(borderorient(c, e->x, e->y) != BorderUnknown){
                         switch (e->button) {
                         case Button1:
                         case Button2:
t@@ -63,11 +62,10 @@ button(XButtonEvent *e)
                         default:
                                 return;
                         }
-            }
+                }
                 e->x += c->x - BORDER;
                 e->y += c->y - BORDER;
-        }
-        else if (e->window != e->root) {
+        } else if (e->window != e->root) {
                 if (debug) fprintf(stderr, "but no client: e x=%d y=%d\n",
                                 e->x, e->y);
                 XTranslateCoordinates(dpy, e->window, s->root, e->x, e->y,
diff --git a/src/cmd/tweak.c b/src/cmd/tweak.c
t@@ -0,0 +1,2058 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+typedef struct        Thing        Thing;
+
+struct Thing
+{
+        Image        *b;
+        Subfont         *s;
+        char                *name;        /* file name */
+        int                face;                /* is 48x48 face file or cursor file*/
+        Rectangle r;                /* drawing region */
+        Rectangle tr;                /* text region */
+        Rectangle er;                /* entire region */
+        long                c;                /* character number in subfont */
+        int                mod;        /* modified */
+        int                mag;                /* magnification */
+        Rune                off;                /* offset for subfont indices */
+        Thing        *parent;        /* thing of which i'm an edit */
+        Thing        *next;
+};
+
+enum
+{
+        Border        = 1,
+        Up                = 1,
+        Down        = 0,
+        Mag                = 4,
+        Maxmag        = 10,
+};
+
+enum
+{
+        NORMAL        =0,
+        FACE        =1,
+        CURSOR        =2
+};
+
+enum
+{
+        Mopen,
+        Mread,
+        Mwrite,
+        Mcopy,
+        Mchar,
+        Mpixels,
+        Mclose,
+        Mexit,
+};
+
+enum
+{
+        Blue        = 54,
+};
+
+char        *menu3str[] = {
+        [Mopen]        "open",
+        [Mread]        "read",
+        [Mwrite]        "write",
+        [Mcopy]        "copy",
+        [Mchar]        "char",
+        [Mpixels]        "pixels",
+        [Mclose]        "close",
+        [Mexit]        "exit",
+        0,
+};
+
+Menu        menu3 = {
+        menu3str
+};
+
+Cursor sweep0 = {
+        {-7, -7},
+        {0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0,
+         0x03, 0xC0, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
+         0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xC0, 0x03, 0xC0,
+         0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0},
+        {0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
+         0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE,
+         0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
+         0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00}
+};
+
+Cursor box = {
+        {-7, -7},
+        {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+         0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
+         0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
+         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+        {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
+         0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+         0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+         0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
+};
+
+Cursor sight = {
+        {-7, -7},
+        {0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
+         0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
+         0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
+         0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,},
+        {0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
+         0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
+         0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
+         0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,}
+};
+
+Cursor pixel = {
+        {-7, -7},
+        {0x1f, 0xf8, 0x3f, 0xfc,  0x7f, 0xfe,  0xf8, 0x1f,
+        0xf0, 0x0f,  0xe0, 0x07, 0xe0, 0x07, 0xfe, 0x7f, 
+        0xfe, 0x7f, 0xe0, 0x07, 0xe0, 0x07, 0xf0, 0x0f, 
+        0x78, 0x1f, 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, },
+        {0x00, 0x00, 0x0f, 0xf0, 0x31, 0x8c, 0x21, 0x84, 
+        0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x40, 0x02, 
+        0x40, 0x02, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 
+        0x21, 0x84, 0x31, 0x8c, 0x0f, 0xf0, 0x00, 0x00, }
+};
+
+Cursor busy = {
+        {-7, -7},
+        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+         0x00, 0x00, 0x00, 0x0c, 0x00, 0x8e, 0x1d, 0xc7,
+         0xff, 0xe3, 0xff, 0xf3, 0xff, 0xff, 0x7f, 0xfe, 
+         0x3f, 0xf8, 0x17, 0xf0, 0x03, 0xe0, 0x00, 0x00,},
+        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+         0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x82,
+         0x04, 0x41, 0xff, 0xe1, 0x5f, 0xf1, 0x3f, 0xfe, 
+         0x17, 0xf0, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00,}
+};
+
+Cursor skull = {
+        {-7,-7},
+        {0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0xe7, 0xe7, 
+         0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0x1f, 0xf8, 
+         0x0f, 0xf0, 0x3f, 0xfc, 0xff, 0xff, 0xff, 0xff, 
+         0xef, 0xf7, 0xc7, 0xe3, 0x00, 0x00, 0x00, 0x00,},
+        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03,
+         0xE7, 0xE7, 0x3F, 0xFC, 0x0F, 0xF0, 0x0D, 0xB0,
+         0x07, 0xE0, 0x06, 0x60, 0x37, 0xEC, 0xE4, 0x27,
+         0xC3, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,}
+};
+
+Rectangle        cntlr;                /* control region */
+Rectangle        editr;                /* editing region */
+Rectangle        textr;                /* text region */
+Thing                *thing;
+Mouse                mouse;
+char                hex[] = "0123456789abcdefABCDEF";
+jmp_buf                err;
+char                *file;
+int                mag;
+int                but1val = 0;
+int                but2val = 255;
+int                invert = 0;
+Image                *values[256];
+Image                *greyvalues[256];
+uchar                data[8192];
+
+Thing*        tget(char*);
+void        mesg(char*, ...);
+void        drawthing(Thing*, int);
+void        xselect(void);
+void        menu(void);
+void        error(Display*, char*);
+void        buttons(int);
+void        drawall(void);
+void        tclose1(Thing*);
+
+void
+main(int argc, char *argv[])
+{
+        int i;
+        Event e;
+        Thing *t;
+
+        mag = Mag;
+        if(initdraw(error, 0, "tweak") < 0){
+                fprint(2, "tweak: initdraw failed: %r\n");
+                exits("initdraw");
+        }
+        for(i=0; i<256; i++){
+                values[i] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, cmap2rgba(i));
+                greyvalues[i] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, (i<<24)|(i<<16)|(i<<8)|0xFF);
+                if(values[i] == 0 || greyvalues[i] == 0)
+                        drawerror(display, "can't allocate image");
+        }
+        einit(Emouse|Ekeyboard);
+        eresized(0);
+        i = 1;
+        setjmp(err);
+        for(; iwhite, nil, ZP);
+        if(thing == 0)
+                return;
+        if(thing != t){
+                for(nt=thing; nt->next!=t; nt=nt->next)
+                        ;
+                draw(screen, Rect(screen->r.min.x, nt->er.max.y, editr.max.x, editr.max.y),
+                        display->white, nil, ZP);
+        }
+        for(nt=t; nt; nt=nt->next){
+                drawthing(nt, 0);
+                if(nt->next == 0){
+                        p = Pt(editr.min.x, nt->er.max.y);
+                        draw(screen, Rpt(p, editr.max), display->white, nil, ZP);
+                }
+        }
+        mesg("");
+}
+
+void
+eresized(int new)
+{
+        if(new && getwindow(display, Refnone) < 0)
+                error(display, "can't reattach to window");
+        cntlr = insetrect(screen->clipr, 1);
+        editr = cntlr;
+        textr = editr;
+        textr.min.y = textr.max.y - font->height;
+        cntlr.max.y = cntlr.min.y + font->height;
+        editr.min.y = cntlr.max.y+1;
+        editr.max.y = textr.min.y-1;
+        draw(screen, screen->clipr, display->white, nil, ZP);
+        draw(screen, Rect(editr.min.x, editr.max.y, editr.max.x+1, editr.max.y+1), display->black, nil, ZP);
+        replclipr(screen, 0, editr);
+        drawall();
+}
+
+void
+mesgstr(Point p, int line, char *s)
+{
+        Rectangle c, r;
+
+        r.min = p;
+        r.min.y += line*font->height;
+        r.max.y = r.min.y+font->height;
+        r.max.x = editr.max.x;
+        c = screen->clipr;
+        replclipr(screen, 0, r);
+        draw(screen, r, values[0xDD], nil, ZP);
+        r.min.x++;
+        string(screen, r.min, display->black, ZP, font, s);
+        replclipr(screen, 0, c);
+        flushimage(display, 1);
+}
+
+void
+mesg(char *fmt, ...)
+{
+        char buf[1024];
+        va_list arg;
+
+        va_start(arg, fmt);
+        vseprint(buf, buf+sizeof(buf), fmt, arg);
+        va_end(arg);
+        mesgstr(textr.min, 0, buf);
+}
+
+void
+tmesg(Thing *t, int line, char *fmt, ...)
+{
+        char buf[1024];
+        va_list arg;
+
+        va_start(arg, fmt);
+        vseprint(buf, buf+sizeof(buf), fmt, arg);
+        va_end(arg);
+        mesgstr(t->tr.min, line, buf);
+}
+
+
+void
+scntl(char *l)
+{
+        sprint(l, "mag: %d  but1: %d  but2: %d  invert-on-copy: %c", mag, but1val, but2val, "ny"[invert]);
+}
+
+void
+cntl(void)
+{
+        char buf[256];
+
+        scntl(buf);
+        mesgstr(cntlr.min, 0, buf);
+}
+
+void
+stext(Thing *t, char *l0, char *l1)
+{
+        Fontchar *fc;
+        char buf[256];
+
+        l1[0] = 0;
+        sprint(buf, "depth:%d r:%d %d  %d %d ", 
+                t->b->depth, t->b->r.min.x, t->b->r.min.y,
+                t->b->r.max.x, t->b->r.max.y);
+        if(t->parent)
+                sprint(buf+strlen(buf), "mag: %d ", t->mag);
+        sprint(l0, "%s file: %s", buf, t->name);
+        if(t->c >= 0){
+                fc = &t->parent->s->info[t->c];
+                sprint(l1, "c(hex): %x c(char): %C x: %d "
+                           "top: %d bottom: %d left: %d width: %d iwidth: %d",
+                        (int)(t->c+t->parent->off), (int)(t->c+t->parent->off),
+                        fc->x, fc->top, fc->bottom, fc->left,
+                        fc->width, Dx(t->b->r));
+        }else if(t->s)
+                sprint(l1, "offset(hex): %ux n:%d  height:%d  ascent:%d",
+                        t->off, t->s->n, t->s->height, t->s->ascent);
+}
+
+void
+text(Thing *t)
+{
+        char l0[256], l1[256];
+
+        stext(t, l0, l1);
+        tmesg(t, 0, l0);
+        if(l1[0])
+                tmesg(t, 1, l1);
+}
+
+void
+drawall(void)
+{
+        Thing *t;
+
+        cntl();
+        for(t=thing; t; t=t->next)
+                drawthing(t, 0);
+}
+
+int
+value(Image *b, int x)
+{
+        int v, l, w;
+        uchar mask;
+
+        w = b->depth;
+        if(w > 8){
+                mesg("ldepth too large");
+                return 0;
+        }
+        l = xlog2(w);
+        mask = (1<r.min.x&~(7>>l);        /* adjust x relative to first pixel */
+        v = data[x>>(3-l)];
+        v >>= ((7>>l)<>l))< screen->depth)
+                v >>= d - screen->depth;
+        else
+                while(d < screen->depth && d < 8){
+                        v |= v << d;
+                        d <<= 1;
+                }
+        if(v<0 || v>255){
+                mesg("internal error: bad color");
+                return Blue;
+        }
+        return v;
+}
+
+void
+drawthing(Thing *nt, int link)
+{
+        int n, nl, nf, i, x, y, sx, sy, fdx, dx, dy, v;
+        Thing *t;
+        Subfont *s;
+        Image *b, *col;
+        Point p, p1, p2;
+
+        if(link){
+                nt->next = 0;
+                if(thing == 0){
+                        thing = nt;
+                        y = editr.min.y;
+                }else{
+                        for(t=thing; t->next; t=t->next)
+                                ;
+                        t->next = nt;
+                        y = t->er.max.y;
+                }
+        }else{
+                if(thing == nt)
+                        y = editr.min.y;
+                else{
+                        for(t=thing; t->next!=nt; t=t->next)
+                                ;
+                        y = t->er.max.y;
+                }
+        }
+        s = nt->s;
+        b = nt->b;
+        nl = font->height;
+        if(s || nt->c>=0)
+                nl += font->height;
+        fdx = Dx(editr) - 2*Border;
+        dx = Dx(b->r);
+        dy = Dy(b->r);
+        if(nt->mag > 1){
+                dx *= nt->mag;
+                dy *= nt->mag;
+                fdx -= fdx%nt->mag;
+        }
+        nf = 1 + dx/fdx;
+        nt->er.min.y = y;
+        nt->er.min.x = editr.min.x;
+        nt->er.max.x = nt->er.min.x + Border + dx + Border;
+        if(nt->er.max.x > editr.max.x)
+                nt->er.max.x = editr.max.x;
+        nt->er.max.y = nt->er.min.y + Border + nf*(dy+Border);
+        nt->r = insetrect(nt->er, Border);
+        nt->er.max.x = editr.max.x;
+        draw(screen, nt->er, display->white, nil, ZP);
+        for(i=0; ir.min.x-1, nt->r.min.y+i*(Border+dy));
+                /* draw portion of bitmap */
+                p = Pt(p1.x+1, p1.y);
+                if(nt->mag == 1)
+                        draw(screen, Rect(p.x, p.y, p.x+fdx+Dx(b->r), p.y+Dy(b->r)),
+                                b, nil, Pt(b->r.min.x+i*fdx, b->r.min.y));
+                else{
+                        for(y=b->r.min.y; yr.max.y; y++){
+                                sy = p.y+(y-b->r.min.y)*nt->mag;
+                                if((n=unloadimage(b, Rect(b->r.min.x, y, b->r.max.x, y+1), data, sizeof data)) < 0)
+                                        fprint(2, "unloadimage: %r\n");
+                                for(x=b->r.min.x+i*(fdx/nt->mag); xr.max.x; x++){
+                                        sx = p.x+(x-i*(fdx/nt->mag)-b->r.min.x)*nt->mag;
+                                        if(sx >= nt->r.max.x)
+                                                break;
+                                        v = bvalue(value(b, x), b->depth);
+                                        if(v == 255)
+                                                continue;
+                                        if(b->chan == GREY8)
+                                                draw(screen, Rect(sx, sy, sx+nt->mag, sy+nt->mag),
+                                                        greyvalues[v], nil, ZP);
+                                        else
+                                                draw(screen, Rect(sx, sy, sx+nt->mag, sy+nt->mag),
+                                                        values[v], nil, ZP);
+                                }
+
+                        }
+                }
+                /* line down left */
+                if(i == 0)
+                        col = display->black;
+                else
+                        col = display->white;
+                draw(screen, Rect(p1.x, p1.y, p1.x+1, p1.y+dy+Border), col, nil, ZP);
+                /* line across top */
+                draw(screen, Rect(p1.x, p1.y-1, nt->r.max.x+Border, p1.y), display->black, nil, ZP);
+                p2 = p1;
+                if(i == nf-1){
+                        p2.x += 1 + dx%fdx;
+                        col = display->black;
+                }else{
+                        p2.x = nt->r.max.x;
+                        col = display->white;
+                }
+                /* line down right */
+                draw(screen, Rect(p2.x, p2.y, p2.x+1, p2.y+dy+Border), col, nil, ZP);
+                /* line across bottom */
+                if(i == nf-1){
+                        p1.y += Border+dy;
+                        draw(screen, Rect(p1.x, p1.y-1, p2.x,p1.y), display->black, nil, ZP);
+                }
+        }
+        nt->tr.min.x = editr.min.x;
+        nt->tr.max.x = editr.max.x;
+        nt->tr.min.y = nt->er.max.y + Border;
+        nt->tr.max.y = nt->tr.min.y + nl;
+        nt->er.max.y = nt->tr.max.y + Border;
+        text(nt);
+}
+
+int
+tohex(int c)
+{
+        if('0'<=c && c<='9')
+                return c - '0';
+        if('a'<=c && c<='f')
+                return 10 + (c - 'a');
+        if('A'<=c && c<='F')
+                return 10 + (c - 'A');
+        return 0;
+}
+
+Thing*
+tget(char *file)
+{
+        int i, j, fd, face, x, y, c, chan;
+        Image *b;
+        Subfont *s;
+        Thing *t;
+        Dir *d;
+        jmp_buf oerr;
+        uchar buf[256];
+        char *data;
+
+        buf[0] = '\0';
+        errstr((char*)buf, sizeof buf);        /* flush pending error message */
+        memmove(oerr, err, sizeof err);
+        d = nil;
+        if(setjmp(err)){
+   Err:
+                free(d);
+                memmove(err, oerr, sizeof err);
+                return 0;
+        }
+        fd = open(file, OREAD);
+        if(fd < 0){
+                mesg("can't open %s: %r", file);
+                goto Err;
+        }
+        d = dirfstat(fd);
+        if(d == nil){
+                mesg("can't stat bitmap file %s: %r", file);
+                close(fd);
+                goto Err;
+        }
+        if(read(fd, buf, 11) != 11){
+                mesg("can't read %s: %r", file);
+                close(fd);
+                goto Err;
+        }
+        seek(fd, 0, 0);
+        data = (char*)buf;
+        if(*data == '{')
+                data++;
+        if(memcmp(data, "0x", 2)==0 && data[4]==','){
+                /*
+                 * cursor file
+                 */
+                face = CURSOR;
+                s = 0;
+                data = malloc(d->length+1);
+                if(data == 0){
+                        mesg("can't malloc buffer: %r");
+                        close(fd);
+                        goto Err;
+                }
+                data[d->length] = 0;
+                if(read(fd, data, d->length) != d->length){
+                        mesg("can't read cursor file %s: %r", file);
+                        close(fd);
+                        goto Err;
+                }
+                b = allocimage(display, Rect(0, 0, 16, 32), GREY1, 0, DNofill);
+                if(b == 0){
+                        mesg("image alloc failed file %s: %r", file);
+                        free(data);
+                        close(fd);
+                        goto Err;
+                }
+                i = 0;
+                for(x=0;x<64; ){
+                        if((c=data[i]) == '\0')
+                                goto ill;
+                        if(c=='0' && data[i+1] == 'x'){
+                                i += 2;
+                                continue;
+                        }
+                        if(strchr(hex, c)){
+                                buf[x++] = (tohex(c)<<4) | tohex(data[i+1]);
+                                i += 2;
+                                continue;
+                        }
+                        i++;
+                }
+                loadimage(b, Rect(0, 0, 16, 32), buf, sizeof buf);
+                free(data);
+        }else if(memcmp(buf, "0x", 2)==0){
+                /*
+                 * face file
+                 */
+                face = FACE;
+                s = 0;
+                data = malloc(d->length+1);
+                if(data == 0){
+                        mesg("can't malloc buffer: %r");
+                        close(fd);
+                        goto Err;
+                }
+                data[d->length] = 0;
+                if(read(fd, data, d->length) != d->length){
+                        mesg("can't read bitmap file %s: %r", file);
+                        close(fd);
+                        goto Err;
+                }
+                for(y=0,i=0; ilength; i++)
+                        if(data[i] == '\n')
+                                y++;
+                if(y == 0){
+        ill:
+                        mesg("ill-formed face file %s", file);
+                        close(fd);
+                        free(data);
+                        goto Err;
+                }
+                for(x=0,i=0; (c=data[i])!='\n'; ){
+                        if(c==',' || c==' ' || c=='\t'){
+                                i++;
+                                continue;
+                        }
+                        if(c=='0' && data[i+1] == 'x'){
+                                i += 2;
+                                continue;
+                        }
+                        if(strchr(hex, c)){
+                                x += 4;
+                                i++;
+                                continue;
+                        }
+                        goto ill;
+                }
+                if(x % y)
+                        goto ill;
+                switch(x / y){
+                default:
+                        goto ill;
+                case 1:
+                        chan = GREY1;
+                        break;
+                case 2:
+                        chan = GREY2;
+                        break;
+                case 4:
+                        chan = GREY4;
+                        break;
+                case 8:
+                        chan = CMAP8;
+                        break;
+                }
+                b = allocimage(display, Rect(0, 0, y, y), chan, 0, -1);
+                if(b == 0){
+                        mesg("image alloc failed file %s: %r", file);
+                        free(data);
+                        close(fd);
+                        goto Err;
+                }
+                i = 0;
+                for(j=0; jlength)
+                        s = readsubfonti(display, file, fd, b, 0);
+        }
+        close(fd);
+        t = malloc(sizeof(Thing));
+        if(t == 0){
+   nomem:
+                mesg("malloc failed: %r");
+                if(s)
+                        freesubfont(s);
+                else
+                        freeimage(b);
+                goto Err;
+        }
+        t->name = strdup(file);
+        if(t->name == 0){
+                free(t);
+                goto nomem;
+        }
+        t->b = b;
+        t->s = s;
+        t->face = face;
+        t->mod = 0;
+        t->parent = 0;
+        t->c = -1;
+        t->mag = 1;
+        t->off = 0;
+        memmove(err, oerr, sizeof err);
+        return t;
+}
+
+int
+atline(int x, Point p, char *line, char *buf)
+{
+        char *s, *c, *word, *hit;
+        int w, wasblank;
+        Rune r;
+
+        wasblank = 1;
+        hit = 0;
+        word = 0;
+        for(s=line; *s; s+=w){
+                w = chartorune(&r, s);
+                x += runestringnwidth(font, &r, 1);
+                if(wasblank && r!=' ')
+                        word = s;
+                wasblank = 0;
+                if(r == ' '){
+                        if(x >= p.x)
+                                break;
+                        wasblank = 1;
+                }
+                if(r == ':')
+                        hit = word;
+        }
+        if(x < p.x)
+                return 0;
+        c = utfrune(hit, ':');
+        strncpy(buf, hit, c-hit);
+        buf[c-hit] = 0;
+        return 1;
+}
+
+int
+attext(Thing *t, Point p, char *buf)
+{
+        char l0[256], l1[256];
+
+        if(!ptinrect(p, t->tr))
+                return 0;
+        stext(t, l0, l1);
+        if(p.y < t->tr.min.y+font->height)
+                return atline(t->r.min.x, p, l0, buf);
+        else
+                return atline(t->r.min.x, p, l1, buf);
+}
+
+int
+type(char *buf, char *tag)
+{
+        Rune r;
+        char *p;
+
+        esetcursor(&busy);
+        p = buf;
+        for(;;){
+                *p = 0;
+                mesg("%s: %s", tag, buf);
+                r = ekbd();
+                switch(r){
+                case '\n':
+                        mesg("");
+                        esetcursor(0);
+                        return p-buf;
+                case 0x15:        /* control-U */
+                        p = buf;
+                        break;
+                case '\b':
+                        if(p > buf)
+                                --p;
+                        break;
+                default:
+                        p += runetochar(p, &r);
+                }
+        }
+        return 0;        /* shut up compiler */
+}
+
+void
+textedit(Thing *t, char *tag)
+{
+        char buf[256];
+        char *s;
+        Image *b;
+        Subfont *f;
+        Fontchar *fc, *nfc;
+        Rectangle r;
+        ulong chan;
+        int i, ld, d, w, c, doredraw, fdx, x;
+        Thing *nt;
+
+        buttons(Up);
+        if(type(buf, tag) == 0)
+                return;
+        if(strcmp(tag, "file") == 0){
+                for(s=buf; *s; s++)
+                        if(*s <= ' '){
+                                mesg("illegal file name");
+                                return;
+                        }
+                if(strcmp(t->name, buf) != 0){
+                        if(t->parent)
+                                t->parent->mod = 1;
+                        else
+                                t->mod = 1;
+                }
+                for(nt=thing; nt; nt=nt->next)
+                        if(t==nt || t->parent==nt || nt->parent==t){
+                                free(nt->name);
+                                nt->name = strdup(buf);
+                                if(nt->name == 0){
+                                        mesg("malloc failed: %r");
+                                        return;
+                                }
+                                text(nt);
+                        }
+                return;
+        }
+        if(strcmp(tag, "depth") == 0){
+                if(buf[0]<'0' || '9'8 || xlog2(d)<0){
+                        mesg("illegal ldepth");
+                        return;
+                }
+                if(d == t->b->depth)
+                        return;
+                if(t->parent)
+                        t->parent->mod = 1;
+                else
+                        t->mod = 1;
+                if(d == 8)
+                        chan = CMAP8;
+                else
+                        chan = CHAN1(CGrey, d);
+                for(nt=thing; nt; nt=nt->next){
+                        if(nt!=t && nt!=t->parent && nt->parent!=t)
+                                continue;
+                        b = allocimage(display, nt->b->r, chan, 0, 0);
+                        if(b == 0){
+        nobmem:
+                                mesg("image alloc failed: %r");
+                                return;
+                        }
+                        draw(b, b->r, nt->b, nil, nt->b->r.min);
+                        freeimage(nt->b);
+                        nt->b = b;
+                        if(nt->s){
+                                b = allocimage(display, nt->b->r, chan, 0, -1);
+                                if(b == 0)
+                                        goto nobmem;
+                                draw(b, b->r, nt->b, nil, nt->b->r.min);
+                                f = allocsubfont(t->name, nt->s->n, nt->s->height, nt->s->ascent, nt->s->info, b);
+                                if(f == 0){
+        nofmem:
+                                        freeimage(b);
+                                        mesg("can't make subfont: %r");
+                                        return;
+                                }
+                                nt->s->info = 0;        /* prevent it being freed */
+                                nt->s->bits = 0;
+                                freesubfont(nt->s);
+                                nt->s = f;
+                        }
+                        drawthing(nt, 0);
+                }
+                return;
+        }
+        if(strcmp(tag, "mag") == 0){
+                if(buf[0]<'0' || '9'Maxmag){
+                        mesg("illegal magnification");
+                        return;
+                }
+                if(t->mag == ld)
+                        return;
+                t->mag = ld;
+                redraw(t);
+                return;
+        }
+        if(strcmp(tag, "r") == 0){
+                if(t->s){
+                        mesg("can't change rectangle of subfont\n");
+                        return;
+                }
+                s = buf;
+                r.min.x = strtoul(s, &s, 0);
+                r.min.y = strtoul(s, &s, 0);
+                r.max.x = strtoul(s, &s, 0);
+                r.max.y = strtoul(s, &s, 0);
+                if(Dx(r)<=0 || Dy(r)<=0){
+                        mesg("illegal rectangle");
+                        return;
+                }
+                if(t->parent)
+                        t = t->parent;
+                for(nt=thing; nt; nt=nt->next){
+                        if(nt->parent==t && !rectinrect(nt->b->r, r))
+                                tclose1(nt);
+                }
+                b = allocimage(display, r, t->b->chan, 0, 0);
+                if(b == 0)
+                        goto nobmem;
+                draw(b, r, t->b, nil, r.min);
+                freeimage(t->b);
+                t->b = b;
+                b = allocimage(display, r, t->b->chan, 0, 0);
+                if(b == 0)
+                        goto nobmem;
+                redraw(t);
+                t->mod = 1;
+                return;
+        }
+        if(strcmp(tag, "ascent") == 0){
+                if(buf[0]<'0' || '9't->s->height){
+                        mesg("illegal ascent");
+                        return;
+                }
+                if(t->s->ascent == ld)
+                        return;
+                t->s->ascent = ld;
+                text(t);
+                t->mod = 1;
+                return;
+        }
+        if(strcmp(tag, "height") == 0){
+                if(buf[0]<'0' || '9's->height == ld)
+                        return;
+                t->s->height = ld;
+                text(t);
+                t->mod = 1;
+                return;
+        }
+        if(strcmp(tag, "left")==0 || strcmp(tag, "width") == 0){
+                if(buf[0]<'0' || '9'parent->s->info[t->c];
+                if(strcmp(tag, "left")==0){
+                        if(fc->left == ld)
+                                return;
+                        fc->left = ld;
+                }else{
+                        if(fc->width == ld)
+                                return;
+                        fc->width = ld;
+                }
+                text(t);
+                t->parent->mod = 1;
+                return;
+        }
+        if(strcmp(tag, "offset(hex)") == 0){
+                if(!strchr(hex, buf[0])){
+        illoff:
+                        mesg("illegal offset");
+                        return;
+                }
+                s = 0;
+                ld = strtoul(buf, &s, 16);
+                if(*s)
+                        goto illoff;
+                t->off = ld;
+                text(t);
+                for(nt=thing; nt; nt=nt->next)
+                        if(nt->parent == t)
+                                text(nt);
+                return;
+        }
+        if(strcmp(tag, "n") == 0){
+                if(buf[0]<'0' || '9's;
+                if(w == f->n)
+                        return;
+                doredraw = 0;
+        again:
+                for(nt=thing; nt; nt=nt->next)
+                        if(nt->parent == t){
+                                doredraw = 1;
+                                tclose1(nt);
+                                goto again;
+                        }
+                r = t->b->r;
+                if(w < f->n)
+                        r.max.x = f->info[w].x;
+                b = allocimage(display, r, t->b->chan, 0, 0);
+                if(b == 0)
+                        goto nobmem;
+                draw(b, b->r, t->b, nil, r.min);
+                fdx = Dx(editr) - 2*Border;
+                if(Dx(t->b->r)/fdx != Dx(b->r)/fdx)
+                        doredraw = 1;
+                freeimage(t->b);
+                t->b = b;
+                b = allocimage(display, r, t->b->chan, 0, 0);
+                if(b == 0)
+                        goto nobmem;
+                draw(b, b->r, t->b, nil, r.min);
+                nfc = malloc((w+1)*sizeof(Fontchar));
+                if(nfc == 0){
+                        mesg("malloc failed");
+                        freeimage(b);
+                        return;
+                }
+                fc = f->info;
+                for(i=0; i<=w && i<=f->n; i++)
+                        nfc[i] = fc[i];
+                if(w+1 < i)
+                        memset(nfc+i, 0, ((w+1)-i)*sizeof(Fontchar));
+                x = fc[f->n].x;
+                for(; i<=w; i++)
+                        nfc[i].x = x;
+                f = allocsubfont(t->name, w, f->height, f->ascent, nfc, b);
+                if(f == 0)
+                        goto nofmem;
+                t->s->bits = nil;        /* don't free it */
+                freesubfont(t->s);
+                f->info = nfc;
+                t->s = f;
+                if(doredraw)
+                        redraw(thing);
+                else
+                        drawthing(t, 0);
+                t->mod = 1;
+                return;
+        }
+        if(strcmp(tag, "iwidth") == 0){
+                if(buf[0]<'0' || '9'b->r);
+                if(w == 0)
+                        return;
+                r = t->parent->b->r;
+                r.max.x += w;
+                c = t->c;
+                t = t->parent;
+                f = t->s;
+                b = allocimage(display, r, t->b->chan, 0, 0);
+                if(b == 0)
+                        goto nobmem;
+                fc = &f->info[c];
+                draw(b, Rect(b->r.min.x, b->r.min.y,
+                                b->r.min.x+(fc[1].x-t->b->r.min.x), b->r.min.y+Dy(t->b->r)),
+                                t->b, nil, t->b->r.min);
+                draw(b, Rect(fc[1].x+w, b->r.min.y, w+t->b->r.max.x, b->r.min.y+Dy(t->b->r)),
+                        t->b, nil, Pt(fc[1].x, t->b->r.min.y));
+                fdx = Dx(editr) - 2*Border;
+                doredraw = 0;
+                if(Dx(t->b->r)/fdx != Dx(b->r)/fdx)
+                        doredraw = 1;
+                freeimage(t->b);
+                t->b = b;
+                b = allocimage(display, r, t->b->chan, 0, 0);
+                if(b == 0)
+                        goto nobmem;
+                draw(b, b->r, t->b, nil, t->b->r.min);
+                fc = &f->info[c+1];
+                for(i=c+1; i<=f->n; i++, fc++)
+                        fc->x += w;
+                f = allocsubfont(t->name, f->n, f->height, f->ascent,
+                        f->info, b);
+                if(f == 0)
+                        goto nofmem;
+                /* t->s and f share info; free carefully */
+                fc = f->info;
+                t->s->bits = nil;
+                t->s->info = 0;
+                freesubfont(t->s);
+                f->info = fc;
+                t->s = f;
+                if(doredraw)
+                        redraw(t);
+                else
+                        drawthing(t, 0);
+                /* redraw all affected chars */
+                for(nt=thing; nt; nt=nt->next){
+                        if(nt->parent!=t || nt->cinfo[nt->c];
+                        r.min.x = fc[0].x;
+                        r.min.y = nt->b->r.min.y;
+                        r.max.x = fc[1].x;
+                        r.max.y = nt->b->r.max.y;
+                        b = allocimage(display, r, nt->b->chan, 0, 0);
+                        if(b == 0)
+                                goto nobmem;
+                        draw(b, r, t->b, nil, r.min);
+                        doredraw = 0;
+                        if(Dx(nt->b->r)/fdx != Dx(b->r)/fdx)
+                                doredraw = 1;
+                        freeimage(nt->b);
+                        nt->b = b;
+                        if(c != nt->c)
+                                text(nt);
+                        else{
+                                if(doredraw)
+                                        redraw(nt);
+                                else
+                                        drawthing(nt, 0);
+                        }
+                }
+                t->mod = 1;
+                return;
+        }
+        mesg("cannot edit %s in file %s", tag, t->name);
+}
+
+void
+cntledit(char *tag)
+{
+        char buf[256];
+        ulong l;
+
+        buttons(Up);
+        if(type(buf, tag) == 0)
+                return;
+        if(strcmp(tag, "mag") == 0){
+                if(buf[0]<'0' || '9'Maxmag){
+                        mesg("illegal magnification");
+                        return;
+                }
+                mag = l;
+                cntl();
+                return;
+        }
+        if(strcmp(tag, "but1")==0
+        || strcmp(tag, "but2")==0){
+                if(buf[0]<'0' || '9'255){
+                        mesg("illegal value");
+                        return;
+                }
+                if(strcmp(tag, "but1") == 0)
+                        but1val = l;
+                else if(strcmp(tag, "but2") == 0)
+                        but2val = l;
+                cntl();
+                return;
+        }
+        if(strcmp(tag, "invert-on-copy")==0){
+                if(buf[0]=='y' || buf[0]=='1')
+                        invert = 1;
+                else if(buf[0]=='n' || buf[0]=='0')
+                        invert = 0;
+                else{
+                        mesg("illegal value");
+                        return;
+                }
+                cntl();
+                return;
+        }
+        mesg("cannot edit %s", tag);
+}
+
+void
+buttons(int ud)
+{
+        while((mouse.buttons==0) != ud)
+                mouse = emouse();
+}
+
+Point
+screenpt(Thing *t, Point realp)
+{
+        int fdx, n;
+        Point p;
+
+        fdx = Dx(editr)-2*Border;
+        if(t->mag > 1)
+                fdx -= fdx%t->mag;
+        p = mulpt(subpt(realp, t->b->r.min), t->mag);
+        if(fdx < Dx(t->b->r)*t->mag){
+                n = p.x/fdx;
+                p.y += n * (Dy(t->b->r)*t->mag+Border);
+                p.x -= n * fdx;
+        }
+        p = addpt(p, t->r.min);
+        return p;
+}
+
+Point
+realpt(Thing *t, Point screenp)
+{
+        int fdx, n, dy;
+        Point p;
+
+        fdx = (Dx(editr)-2*Border);
+        if(t->mag > 1)
+                fdx -= fdx%t->mag;
+        p.y = screenp.y-t->r.min.y;
+        p.x = 0;
+        if(fdx < Dx(t->b->r)*t->mag){
+                dy = Dy(t->b->r)*t->mag+Border;
+                n = (p.y/dy);
+                p.x = n * fdx;
+                p.y -= n * dy;
+        }
+        p.x += screenp.x-t->r.min.x;
+        p = addpt(divpt(p, t->mag), t->b->r.min);
+        return p;
+}
+
+int
+sweep(int but, Rectangle *r)
+{
+        Thing *t;
+        Point p, q, lastq;
+
+        esetcursor(&sweep0);
+        buttons(Down);
+        if(mouse.buttons != (1<<(but-1))){
+                buttons(Up);
+                esetcursor(0);
+                return 0;
+        }
+        p = mouse.xy;
+        for(t=thing; t; t=t->next)
+                if(ptinrect(p, t->r))
+                        break;
+        if(t)
+                p = screenpt(t, realpt(t, p));
+        r->min = p;
+        r->max = p;
+        esetcursor(&box);
+        lastq = ZP;
+        while(mouse.buttons == (1<<(but-1))){
+                edrawgetrect(insetrect(*r, -Borderwidth), 1);
+                mouse = emouse();
+                edrawgetrect(insetrect(*r, -Borderwidth), 0);
+                q = mouse.xy;
+                if(t)
+                        q = screenpt(t, realpt(t, q));
+                if(eqpt(q, lastq))
+                        continue;
+                *r = canonrect(Rpt(p, q));
+                lastq = q;
+        }
+        esetcursor(0);
+        if(mouse.buttons){
+                buttons(Up);
+                return 0;
+        }
+        return 1;
+}
+
+void
+openedit(Thing *t, Point pt, int c)
+{
+        int x, y;
+        Point p;
+        Rectangle r;
+        Rectangle br;
+        Fontchar *fc;
+        Thing *nt;
+
+        if(t->b->depth > 8){
+                mesg("image has depth %d; can't handle >8", t->b->depth);
+                return;
+        }
+        br = t->b->r;
+        if(t->s == 0){
+                c = -1; 
+                /* if big enough to bother, sweep box */
+                if(Dx(br)<=16 && Dy(br)<=16)
+                        r = br;
+                else{
+                        if(!sweep(1, &r))
+                                return;
+                        r = rectaddpt(r, subpt(br.min, t->r.min));
+                        if(!rectclip(&r, br))
+                                return;
+                        if(Dx(br) <= 8){
+                                r.min.x = br.min.x;
+                                r.max.x = br.max.x;
+                        }else if(Dx(r) < 4){
+            toosmall:
+                                mesg("rectangle too small");
+                                return;
+                        }
+                        if(Dy(br) <= 8){
+                                r.min.y = br.min.y;
+                                r.max.y = br.max.y;
+                        }else if(Dy(r) < 4)
+                                goto toosmall;
+                }
+        }else if(c >= 0){
+                fc = &t->s->info[c];
+                r.min.x = fc[0].x;
+                r.min.y = br.min.y;
+                r.max.x = fc[1].x;
+                r.max.y = br.min.y + Dy(br);
+        }else{
+                /* just point at character */
+                fc = t->s->info;
+                p = addpt(pt, subpt(br.min, t->r.min));
+                x = br.min.x;
+                y = br.min.y;
+                for(c=0; cs->n; c++,fc++){
+            again:
+                        r.min.x = x;
+                        r.min.y = y;
+                        r.max.x = x + fc[1].x - fc[0].x;
+                        r.max.y = y + Dy(br);
+                        if(ptinrect(p, r))
+                                goto found;
+                        if(r.max.x >= br.min.x+Dx(t->r)){
+                                x -= Dx(t->r);
+                                y += t->s->height;
+                                if(fc[1].x > fc[0].x)
+                                        goto again;
+                        }
+                        x += fc[1].x - fc[0].x;
+                }
+                return;
+           found:
+                r = br;
+                r.min.x = fc[0].x;
+                r.max.x = fc[1].x;
+        }
+        nt = malloc(sizeof(Thing));
+        if(nt == 0){
+   nomem:
+                mesg("can't allocate: %r");
+                return;
+        }
+        memset(nt, 0, sizeof(Thing));
+        nt->c = c;
+        nt->b = allocimage(display, r, t->b->chan, 0, DNofill);
+        if(nt->b == 0){
+                free(nt);
+                goto nomem;
+        }
+        draw(nt->b, r, t->b, nil, r.min);
+        nt->name = strdup(t->name);
+        if(nt->name == 0){
+                freeimage(nt->b);
+                free(nt);
+                goto nomem;
+        }
+        nt->parent = t;
+        nt->mag = mag;
+        drawthing(nt, 1);
+}
+
+void
+ckinfo(Thing *t, Rectangle mod)
+{
+        int i, j, k, top, bot, n, zero;
+        Fontchar *fc;
+        Rectangle r;
+        Image *b;
+        Thing *nt;
+
+        if(t->parent)
+                t = t->parent;
+        if(t->s==0 || Dy(t->b->r)==0)
+                return;
+        b = 0;
+        /* check bounding boxes */
+        fc = &t->s->info[0];
+        r.min.y = t->b->r.min.y;
+        r.max.y = t->b->r.max.y;
+        for(i=0; is->n; i++, fc++){
+                r.min.x = fc[0].x;
+                r.max.x = fc[1].x;
+                if(!rectXrect(mod, r))
+                        continue;
+                if(b==0 || Dx(b->r)b->chan, 0, 0);
+                        if(b == 0){
+                                mesg("can't alloc image");
+                                break;
+                        }
+                }
+                draw(b, b->r, display->white, nil, ZP);
+                draw(b, b->r, t->b, nil, r.min);
+                top = 100000;
+                bot = 0;
+                n = 2+((Dx(r)/8)*t->b->depth);
+                for(j=0; jr.max.y; j++){
+                        memset(data, 0, n);
+                        unloadimage(b, Rect(b->r.min.x, j, b->r.max.x, j+1), data, sizeof data);
+                        zero = 1;
+                        for(k=0; k j)
+                                        top = j;
+                                bot = j+1;
+                        }
+                }
+                if(top > j)
+                        top = 0;
+                if(top!=fc->top || bot!=fc->bottom){
+                        fc->top = top;
+                        fc->bottom = bot;
+                        for(nt=thing; nt; nt=nt->next)
+                                if(nt->parent==t && nt->c==i)
+                                        text(nt);
+                }
+        }
+        if(b)
+                freeimage(b);
+}
+
+void
+twidpix(Thing *t, Point p, int set)
+{
+        Image *b, *v;
+        int c;
+
+        b = t->b;
+        if(!ptinrect(p, b->r))
+                return;
+        if(set)
+                c = but1val;
+        else
+                c = but2val;
+        if(b->chan == GREY8)
+                v = greyvalues[c];
+        else
+                v = values[c];
+        draw(b, Rect(p.x, p.y, p.x+1, p.y+1), v, nil, ZP);
+        p = screenpt(t, p);
+        draw(screen, Rect(p.x, p.y, p.x+t->mag, p.y+t->mag), v, nil, ZP);
+}
+
+void
+twiddle(Thing *t)
+{
+        int set;
+        Point p, lastp;
+        Image *b;
+        Thing *nt;
+        Rectangle mod;
+
+        if(mouse.buttons!=1 && mouse.buttons!=2){
+                buttons(Up);
+                return;
+        }
+        set = mouse.buttons==1;
+        b = t->b;
+        lastp = addpt(b->r.min, Pt(-1, -1));
+        mod = Rpt(addpt(b->r.max, Pt(1, 1)), lastp);
+        while(mouse.buttons){
+                p = realpt(t, mouse.xy);
+                if(!eqpt(p, lastp)){
+                        lastp = p;
+                        if(ptinrect(p, b->r)){
+                                for(nt=thing; nt; nt=nt->next)
+                                        if(nt->parent==t->parent || nt==t->parent)
+                                                twidpix(nt, p, set);
+                                if(t->parent)
+                                        t->parent->mod = 1;
+                                else
+                                        t->mod = 1;
+                                if(p.x < mod.min.x)
+                                        mod.min.x = p.x;
+                                if(p.y < mod.min.y)
+                                        mod.min.y = p.y;
+                                if(p.x >= mod.max.x)
+                                        mod.max.x = p.x+1;
+                                if(p.y >= mod.max.y)
+                                        mod.max.y = p.y+1;
+                        }
+                }
+                mouse = emouse();
+        }
+        ckinfo(t, mod);
+}
+
+void
+xselect(void)
+{
+        Thing *t;
+        char line[128], buf[128];
+        Point p;
+
+        if(ptinrect(mouse.xy, cntlr)){
+                scntl(line);
+                if(atline(cntlr.min.x, mouse.xy, line, buf)){
+                        if(mouse.buttons == 1)
+                                cntledit(buf);
+                        else
+                                buttons(Up);
+                        return;
+                }
+                return;
+        }
+        for(t=thing; t; t=t->next){
+                if(attext(t, mouse.xy, buf)){
+                        if(mouse.buttons == 1)
+                                textedit(t, buf);
+                        else
+                                buttons(Up);
+                        return;
+                }
+                if(ptinrect(mouse.xy, t->r)){
+                        if(t->parent == 0){
+                                if(mouse.buttons == 1){
+                                        p = mouse.xy;
+                                        buttons(Up);
+                                        openedit(t, p, -1);
+                                }else
+                                        buttons(Up);
+                                return;
+                        }
+                        twiddle(t);
+                        return;
+                }
+        }
+}
+
+void
+twrite(Thing *t)
+{
+        int i, j, x, y, fd, ws, ld;
+        Biobuf buf;
+        Rectangle r;
+
+        if(t->parent)
+                t = t->parent;
+        esetcursor(&busy);
+        fd = create(t->name, OWRITE, 0666);
+        if(fd < 0){
+                mesg("can't write %s: %r", t->name);
+                return;
+        }
+        if(t->face && t->b->depth <= 4){
+                r = t->b->r;
+                ld = xlog2(t->b->depth);
+                /* This heuristic reflects peculiarly different formats */
+                ws = 4;
+                if(t->face == 2)        /* cursor file */
+                        ws = 1;
+                else if(Dx(r)<32 || ld==0)
+                        ws = 2;
+                Binit(&buf, fd, OWRITE);
+                if(t->face == CURSOR)
+                        Bprint(&buf, "{");
+                for(y=r.min.y; yb, Rect(r.min.x, y, r.max.x, y+1), data, sizeof data);
+                        j = 0;
+                        for(x=r.min.x; x>ld){
+                                Bprint(&buf, "0x");
+                                for(i=0; iface == CURSOR){
+                                switch(y){
+                                case 3: case 7: case 11: case 19: case 23: case 27:
+                                        Bprint(&buf, "\n ");
+                                        break;
+                                case 15:
+                                        Bprint(&buf, "},\n{");
+                                        break;
+                                case 31:
+                                        Bprint(&buf, "}\n");
+                                        break;
+                                }
+                        }else
+                                Bprint(&buf, "\n");
+                }
+                Bterm(&buf);
+        }else
+                if(writeimage(fd, t->b, 0)<0 || (t->s && writesubfont(fd, t->s)<0)){
+                        close(fd);
+                        mesg("can't write %s: %r", t->name);
+                }
+        t->mod = 0;
+        close(fd);
+        mesg("wrote %s", t->name);
+}
+
+void
+tpixels(void)
+{
+        Thing *t;
+        Point p, lastp;
+
+        esetcursor(&pixel);
+        for(;;){
+                buttons(Down);
+                if(mouse.buttons != 4)
+                        break;
+                for(t=thing; t; t=t->next){
+                        lastp = Pt(-1, -1);
+                        if(ptinrect(mouse.xy, t->r)){
+                                while(ptinrect(mouse.xy, t->r) && mouse.buttons==4){
+                                        p = realpt(t, mouse.xy);
+                                        if(!eqpt(p, lastp)){
+                                                if(p.y != lastp.y)
+                                                        unloadimage(t->b, Rect(t->b->r.min.x, p.y, t->b->r.max.x, p.y+1), data, sizeof data);
+                                                mesg("[%d,%d] = %d=0x%ux", p.x, p.y, value(t->b, p.x), value(t->b, p.x));
+                                                lastp = p;
+                                        }
+                                        mouse = emouse();
+                                }
+                                goto Continue;
+                        }
+                }
+                mouse = emouse();
+    Continue:;
+        }
+        buttons(Up);
+        esetcursor(0);
+}
+
+void
+tclose1(Thing *t)
+{
+        Thing *nt;
+
+        if(t == thing)
+                thing = t->next;
+        else{
+                for(nt=thing; nt->next!=t; nt=nt->next)
+                        ;
+                nt->next = t->next;
+        }
+        do
+                for(nt=thing; nt; nt=nt->next)
+                        if(nt->parent == t){
+                                tclose1(nt);
+                                break;
+                        }
+        while(nt);
+        if(t->s)
+                freesubfont(t->s);
+        else
+                freeimage(t->b);
+        free(t->name);
+        free(t);
+}
+
+void
+tclose(Thing *t)
+{
+        Thing *ct;
+
+        if(t->mod){
+                mesg("%s modified", t->name);
+                t->mod = 0;
+                return;
+        }
+        /* fiddle to save redrawing unmoved things */
+        if(t == thing)
+                ct = 0;
+        else
+                for(ct=thing; ct; ct=ct->next)
+                        if(ct->next==t || ct->next->parent==t)
+                                break;
+        tclose1(t);
+        if(ct)
+                ct = ct->next;
+        else
+                ct = thing;
+        redraw(ct);
+}
+
+void
+tread(Thing *t)
+{
+        Thing *nt, *new;
+        Fontchar *i;
+        Rectangle r;
+        int nclosed;
+
+        if(t->parent)
+                t = t->parent;
+        new = tget(t->name);
+        if(new == 0)
+                return;
+        nclosed = 0;
+    again:
+        for(nt=thing; nt; nt=nt->next)
+                if(nt->parent == t){
+                        if(!rectinrect(nt->b->r, new->b->r)
+                        || new->b->depth!=nt->b->depth){
+    closeit:
+                                nclosed++;
+                                nt->parent = 0;
+                                tclose1(nt);
+                                goto again;
+                        }
+                        if((t->s==0) != (new->s==0))
+                                goto closeit;
+                        if((t->face==0) != (new->face==0))
+                                goto closeit;
+                        if(t->s){        /* check same char */
+                                if(nt->c >= new->s->n)
+                                        goto closeit;
+                                i = &new->s->info[nt->c];
+                                r.min.x = i[0].x;
+                                r.max.x = i[1].x;
+                                r.min.y = new->b->r.min.y;
+                                r.max.y = new->b->r.max.y;
+                                if(!eqrect(r, nt->b->r))
+                                        goto closeit;
+                        }
+                        nt->parent = new;
+                        draw(nt->b, nt->b->r, new->b, nil, nt->b->r.min);
+                }
+        new->next = t->next;
+        if(t == thing)
+                thing = new;
+        else{
+                for(nt=thing; nt->next!=t; nt=nt->next)
+                        ;
+                nt->next = new;
+        }
+        if(t->s)
+                freesubfont(t->s);
+        else
+                freeimage(t->b);
+        free(t->name);
+        free(t);
+        for(nt=thing; nt; nt=nt->next)
+                if(nt==new || nt->parent==new)
+                        if(nclosed == 0)
+                                drawthing(nt, 0);        /* can draw in place */
+                        else{
+                                redraw(nt);        /* must redraw all below */
+                                break;
+                        }
+}
+
+void
+tchar(Thing *t)
+{
+        char buf[256], *p;
+        Rune r;
+        ulong c, d;
+
+        if(t->s == 0){
+                t = t->parent;
+                if(t==0 || t->s==0){
+                        mesg("not a subfont");
+                        return;
+                }
+        }
+        if(type(buf, "char (hex or character or hex-hex)") == 0)
+                return;
+        if(utflen(buf) == 1){
+                chartorune(&r, buf);
+                c = r;
+                d = r;
+        }else{
+                if(!strchr(hex, buf[0])){
+                        mesg("illegal hex character");
+                        return;
+                }
+                c = strtoul(buf, 0, 16);
+                d = c;
+                p = utfrune(buf, '-');
+                if(p){
+                        d = strtoul(p+1, 0, 16);
+                        if(d < c){
+                                mesg("invalid range");
+                                return;
+                        }
+                }
+        }
+        c -= t->off;
+        d -= t->off;
+        while(c <= d){
+                if(c<0 || c>=t->s->n){
+                        mesg("0x%lux not in font %s", c+t->off, t->name);
+                        return;
+                }
+                openedit(t, Pt(0, 0), c);
+                c++;
+        }
+}
+
+void
+apply(void (*f)(Thing*))
+{
+        Thing *t;
+
+        esetcursor(&sight);
+        buttons(Down);
+        if(mouse.buttons == 4)
+                for(t=thing; t; t=t->next)
+                        if(ptinrect(mouse.xy, t->er)){
+                                buttons(Up);
+                                f(t);
+                                break;
+                        }
+        buttons(Up);
+        esetcursor(0);
+}
+
+int
+complement(Image *t)
+{
+        int i, n;
+        uchar *buf;
+
+        n = Dy(t->r)*bytesperline(t->r, t->depth);
+        buf = malloc(n);
+        if(buf == 0)
+                return 0;
+        unloadimage(t, t->r, buf, n);
+        for(i=0; ir, buf, n);
+        free(buf);
+        return 1;
+}
+
+void
+copy(void)
+{
+        Thing *st, *dt, *nt;
+        Rectangle sr, dr, fr;
+        Image *tmp;
+        Point p1, p2;
+        int but, up;
+
+        if(!sweep(3, &sr))
+                return;
+        for(st=thing; st; st=st->next)
+                if(rectXrect(sr, st->r))
+                        break;
+        if(st == 0)
+                return;
+        /* click gives full rectangle */
+        if(Dx(sr)<4 && Dy(sr)<4)
+                sr = st->r;
+        rectclip(&sr, st->r);
+        p1 = realpt(st, sr.min);
+        p2 = realpt(st, Pt(sr.min.x, sr.max.y));
+        up = 0;
+        if(p1.x != p2.x){        /* swept across a fold */
+   onafold:
+                mesg("sweep spans a fold");
+                goto Return;
+        }
+        p2 = realpt(st, sr.max);
+        sr.min = p1;
+        sr.max = p2;
+        fr.min = screenpt(st, sr.min);
+        fr.max = screenpt(st, sr.max);
+        p1 = subpt(p2, p1);        /* diagonal */
+        if(p1.x==0 || p1.y==0)
+                return;
+        border(screen, fr, -1, values[Blue], ZP);
+        esetcursor(&box);
+        for(; mouse.buttons==0; mouse=emouse()){
+                for(dt=thing; dt; dt=dt->next)
+                        if(ptinrect(mouse.xy, dt->er))
+                                break;
+                if(up)
+                        edrawgetrect(insetrect(dr, -Borderwidth), 0);
+                up = 0;
+                if(dt == 0)
+                        continue;
+                dr.max = screenpt(dt, realpt(dt, mouse.xy));
+                dr.min = subpt(dr.max, mulpt(p1, dt->mag));
+                if(!rectXrect(dr, dt->r))
+                        continue;
+                edrawgetrect(insetrect(dr, -Borderwidth), 1);
+                up = 1;
+        }
+        /* if up==1, we had a hit */
+        esetcursor(0);
+        if(up)
+                edrawgetrect(insetrect(dr, -Borderwidth), 0);
+        but = mouse.buttons;
+        buttons(Up);
+        if(!up || but!=4)
+                goto Return;
+        dt = 0;
+        for(nt=thing; nt; nt=nt->next)
+                if(rectXrect(dr, nt->r)){
+                        if(dt){
+                                mesg("ambiguous sweep");
+                                return;
+                        }
+                        dt = nt;
+                }
+        if(dt == 0)
+                goto Return;
+        p1 = realpt(dt, dr.min);
+        p2 = realpt(dt, Pt(dr.min.x, dr.max.y));
+        if(p1.x != p2.x)
+                goto onafold;
+        p2 = realpt(dt, dr.max);
+        dr.min = p1;
+        dr.max = p2;
+
+        if(invert){
+                tmp = allocimage(display, dr, dt->b->chan, 0, 255);
+                if(tmp == 0){
+    nomem:
+                        mesg("can't allocate temporary");
+                        goto Return;
+                }
+                draw(tmp, dr, st->b, nil, sr.min);
+                if(!complement(tmp))
+                        goto nomem;
+                draw(dt->b, dr, tmp, nil, dr.min);
+                freeimage(tmp);
+        }else
+                draw(dt->b, dr, st->b, nil, sr.min);
+        if(dt->parent){
+                draw(dt->parent->b, dr, dt->b, nil, dr.min);
+                dt = dt->parent;
+        }
+        drawthing(dt, 0);
+        for(nt=thing; nt; nt=nt->next)
+                if(nt->parent==dt && rectXrect(dr, nt->b->r)){
+                        draw(nt->b, dr, dt->b, nil, dr.min);
+                        drawthing(nt, 0);
+                }
+        ckinfo(dt, dr);
+        dt->mod = 1;
+
+Return:
+        /* clear blue box */
+        drawthing(st, 0);
+}
+
+void
+menu(void)
+{
+        Thing *t;
+        char *mod;
+        int sel;
+        char buf[256];
+
+        sel = emenuhit(3, &mouse, &menu3);
+        switch(sel){
+        case Mopen:
+                if(type(buf, "file")){
+                        t = tget(buf);
+                        if(t)
+                                drawthing(t, 1);
+                }
+                break;
+        case Mwrite:
+                apply(twrite);
+                break;
+        case Mread:
+                apply(tread);
+                break;
+        case Mchar:
+                apply(tchar);
+                break;
+        case Mcopy:
+                copy();
+                break;
+        case Mpixels:
+                tpixels();
+                break;
+        case Mclose:
+                apply(tclose);
+                break;
+        case Mexit:
+                mod = 0;
+                for(t=thing; t; t=t->next)
+                        if(t->mod){
+                                mod = t->name;
+                                t->mod = 0;
+                        }
+                if(mod){
+                        mesg("%s modified", mod);
+                        break;
+                }
+                esetcursor(&skull);
+                buttons(Down);
+                if(mouse.buttons == 4){
+                        buttons(Up);
+                        exits(0);
+                }
+                buttons(Up);
+                esetcursor(0);
+                break;
+        }
+}