tbug fixes and a new gview - 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 e2713e92abdfd2ac5359f5756ffff112e1069c78
 (DIR) parent 97dafe411ab1f501cea5f1d4a14dbc9be549a767
 (HTM) Author: rsc <devnull@localhost>
       Date:   Sat, 24 Apr 2004 16:34:15 +0000
       
       bug fixes and a new gview
       
       Diffstat:
         A man/man1/gview.1                    |     152 +++++++++++++++++++++++++++++++
         M src/cmd/bc.y                        |       4 +++-
         A src/cmd/draw/gview.c                |    2007 +++++++++++++++++++++++++++++++
       
       3 files changed, 2162 insertions(+), 1 deletion(-)
       ---
 (DIR) diff --git a/man/man1/gview.1 b/man/man1/gview.1
       t@@ -0,0 +1,152 @@
       +.TH GVIEW 1
       +.SH NAME
       +gview \- interactive graph viewer
       +.SH SYNOPSIS
       +.B gview
       +[
       +.B -l
       +.I logfile
       +]
       +[
       +.B -m
       +]
       +[
       +.I file
       +]
       +.SH DESCRIPTION
       +.I Gview
       +reads polygonal lines or a polygonal line drawing from an
       +.B ASCII
       +input file (which defaults to standard input), and views it interactively,
       +with commands to zoom in and out, perform simple editing operations, and
       +display information about points and polylines.  The editing commands can
       +change the color and thickness of the polylines, delete (or undelete)
       +some of them, and optionally rotate and move them.  It is also possible to
       +generate an output file that reflects these changes and is in the same format
       +as the input.
       +.PP
       +Since the
       +.B move
       +and
       +.B rotate
       +commands are undesirable when just viewing a graph, they are only enabled if
       +.I gview
       +is invoked with the
       +.B -m
       +option.
       +.PP
       +Clicking on a polyline with button 1 displays the coordinates and a
       +.I t
       +value that tells how far along the polyline.
       +.IR (t =0
       +at the first vertex,
       +.IR t =1
       +at the first vertex,
       +.IR t =1.5
       +halfway between the second and third vertices, etc.)  The
       +.B -l
       +option generates a log file that lists all points selected in this manner.
       +.PP
       +The most important interactive operations are to
       +.I zoom in
       +by sweeping out a rectangle, or to
       +.I zoom out
       +so that everything currently being displayed shrinks to fit in the swept-out
       +rectangle.  Other options on the button 3 menu are
       +.I unzoom
       +which restores the coordinate system to the default state where everything
       +fits on the screen,
       +.I recenter
       +which takes a point and makes it the center of the window, and
       +.I square up
       +which makes the horizontal and vertical scale factors equal.
       +.PP
       +To take a graph of a function where some part is almost linear and
       +see how it deviates from a straight line, select two points on this
       +part of the graph (i.e., select one with button 1 and then select the
       +other) and then use the
       +.I slant
       +command on the button 3 menu.
       +This slants the coordinate system so that the line between the two
       +selected points appears horizontal (but vertical still means positive
       +.IR y ).
       +Then the
       +.I zoom in
       +command can be used to accentuate deviations from horizontal.
       +There is also an
       +.I unslant
       +command that undoes all of this and goes back to an unslanted coordinate
       +system.
       +.PP
       +There is a
       +.I recolor
       +command on button 3 that lets you select a color and change everything
       +to have that color, and a similar command on button 2 that only affects
       +the selected polyline.  The
       +.I thick
       +or
       +.I thin
       +command on button 2 changes the thickness of the selected polyline
       +and there is also an undo command for such edits.
       +.PP
       +Finally, button 3 had commands to
       +.I read
       +a new input file and display it on top of everything else,
       +.I restack
       +the drawing order (in case lines of different color are drawn on top of
       +each other),
       +.I write
       +everything into an output file, or
       +.I exit
       +the program.
       +.PP
       +Each polyline in an input or output file is a space-delimited
       +.I x
       +.I y
       +coordinate pair on a line by itself, and the polyline is a sequence
       +of such vertices followed by a label.  The label could be just a
       +blank line or it could be a string in double
       +quotes, or virtually any text that does not contain spaces and is
       +on a line by itself.  The label at the end of the last polyline is
       +optional.   It is not legal to have two consecutive labels, since that
       +would denote a zero-vertex polyline and each polyline must have at least
       +one vertex. (One-vertex polylines are useful for scatter plots.)
       +
       +If the label after a polyline can contains the word
       +.B "Thick"
       +or a color name
       +.BR (Red ,
       +.BR Pink ,
       +.BR Dkred ,
       +.BR Orange ,
       +.BR Yellow ,
       +.BR Dkyellow ,
       +.BR Green ,
       +.BR Dkgreen ,
       +.BR Cyan ,
       +.BR Blue ,
       +.BR Ltblue ,
       +.BR Magenta ,
       +.BR Violet ,
       +.BR Gray ,
       +.BR Black ,
       +.BR White ),
       +whichever color name comes first will be used to color the polyline.
       +.SH EXAMPLE
       +To see a graph of the function
       +.IR y = sin( x )/ x ,
       +generate input with an awk script and pipe it into
       +.IR gview :
       +.IP
       +.EX
       +awk 'BEGIN{for(x=.1;x<500;x+=.1)print x,sin(x)/x}' | gview
       +.EE
       +.SH SOURCE
       +.B /usr/local/plan9/src/cmd/draw/gview.c
       +.SH SEE ALSO
       +.IR awk (1)
       +.SH BUGS
       +The user interface for the
       +.I slant
       +command is counter-intuitive.  Perhaps it would be better to have a scheme
       +for sweeping out a parallelogram.
 (DIR) diff --git a/src/cmd/bc.y b/src/cmd/bc.y
       t@@ -6,6 +6,8 @@
                #define        bsp_max        5000
        
                Biobuf        *in;
       +        #define stdin bstdin
       +        #define stdout bstdout
                Biobuf        stdin;
                Biobuf        stdout;
                char        cary[1000];
       t@@ -979,5 +981,5 @@ main(int argc, char **argv)
                dup(p[0], 0);
                close(p[0]);
                close(p[1]);
       -        execl("/bin/dc", "dc", 0);
       +        execlp("dc", "dc", 0);
        }
 (DIR) diff --git a/src/cmd/draw/gview.c b/src/cmd/draw/gview.c
       t@@ -0,0 +1,2007 @@
       +#include        <u.h>
       +#include        <libc.h>
       +#include        <ctype.h>
       +#include        <draw.h>
       +#include        <event.h>
       +#include        <cursor.h>
       +#include        <stdio.h>
       +
       +#define Never        0xffffffff        /* Maximum ulong */
       +#define LOG2  0.301029995664
       +#define Button_bit(b)        (1 << ((b)-1))
       +
       +enum {
       +        But1        = Button_bit(1),/* mouse buttons for events */
       +        But2        = Button_bit(2),
       +        But3        = Button_bit(3),
       +};
       +int cantmv = 1;                        /* disallow rotate and move? 0..1 */
       +int top_border, bot_border, lft_border, rt_border;
       +int lft_border0;                /* lft_border for y-axis labels >0 */
       +int top_left, top_right;        /* edges of top line free space */
       +int Mv_delay = 400;                /* msec for button click vs. button hold down */
       +int Dotrad = 2;                        /* dot radius in pixels */
       +int framewd=1;                        /* line thickness for frame (pixels) */
       +int framesep=1;                        /* distance between frame and surrounding text */
       +int outersep=1;                        /* distance: surrounding text to screen edge */
       +Point sdigit;                        /* size of a digit in the font */
       +Point smaxch;                        /* assume any character in font fits in this */
       +double underscan = .05;                /* fraction of frame initially unused per side */
       +double fuzz = 6;                /* selection tolerance in pixels */
       +int tick_len = 15;                /* length of axis label tick mark in pixels */
       +FILE* logfil = 0;                /* dump selected points here if nonzero */
       +
       +#define labdigs  3                /* allow this many sig digits in axis labels */
       +#define digs10pow 1000                /* pow(10,labdigs) */
       +#define axis_color  clr_im(DLtblue)
       +
       +
       +
       +
       +/********************************* Utilities  *********************************/
       +
       +/* Prepend string s to null-terminated string in n-byte buffer buf[], truncating if
       +   necessary and using a space to separate s from the rest of buf[].
       +*/
       +char* str_insert(char* buf, char* s, int n)
       +{
       +        int blen, slen = strlen(s) + 1;
       +        if (slen >= n)
       +                {strncpy(buf,s,n); buf[n-1]='\0'; return buf;}
       +        blen = strlen(buf);
       +        if (blen >= n-slen)
       +                buf[blen=n-slen-1] = '\0';
       +        memmove(buf+slen, buf, slen+blen+1);
       +        memcpy(buf, s, slen-1);
       +        buf[slen-1] = ' ';
       +        return buf;
       +}
       +
       +/* Alter string smain (without lengthening it) so as to remove the first occurrence of
       +   ssub, assuming ssub is ASCII.  Return nonzero (true) if string smain had to be changed.
       +   In spite of the ASCII-centric appearance, I think this can handle UTF in smain.
       +*/
       +int remove_substr(char* smain, char* ssub)
       +{
       +        char *ss, *s = strstr(smain, ssub);
       +        int n = strlen(ssub);
       +        if (s==0)
       +                return 0;
       +        if (islower(s[n]))
       +                s[0] ^= 32;                        /* probably tolower(s[0]) or toupper(s[0]) */
       +        else {
       +                for (ss=s+n; *ss!=0; s++, ss++)
       +                        *s = *ss;
       +                *s = '\0';
       +        }
       +        return 1;
       +}
       +
       +void adjust_border(Font* f)
       +{
       +        int sep = framesep + outersep;
       +        sdigit = stringsize(f, "8");
       +        smaxch = stringsize(f, "MMMg");
       +        smaxch.x = (smaxch.x + 3)/4;
       +        lft_border0 = (1+labdigs)*sdigit.x + framewd + sep;
       +        rt_border = (lft_border0 - sep)/2 + outersep;
       +        bot_border = sdigit.y + framewd + sep;
       +        top_border = smaxch.y + framewd + sep;
       +        lft_border = lft_border0;                /* this gets reset later */
       +}
       +
       +
       +int is_off_screen(Point p)
       +{
       +        const Rectangle* r = &(screen->r);
       +        return p.x-r->min.x<lft_border || r->max.x-p.x<rt_border
       +                || p.y-r->min.y<=top_border || r->max.y-p.y<=bot_border;
       +}
       +
       +
       +Cursor        bullseye =
       +{
       +        {-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,
       +        }
       +};
       +
       +int get_1click(int but, Mouse* m, Cursor* curs)
       +{
       +        if (curs)
       +                esetcursor(curs);
       +        while (m->buttons==0)
       +                *m = emouse();
       +        if (curs)
       +                esetcursor(0);
       +        return (m->buttons==Button_bit(but));
       +}
       +
       +
       +/* Wait until but goes up or until a mouse event's msec passes tlimit.
       +   Return a boolean result that tells whether the button went up.
       +*/
       +int lift_button(int but, Mouse* m, int tlimit)
       +{
       +        do {        *m = emouse();
       +                if (m->msec >= tlimit)
       +                        return 0;
       +        } while (m->buttons & Button_bit(but));
       +        return 1;
       +}
       +
       +
       +/* Set *m to the last pending mouse event, or the first one where but is up.
       +   If no mouse events are pending, wait for the next one.
       +*/
       +void latest_mouse(int but, Mouse* m)
       +{
       +        int bbit = Button_bit(but);
       +        do {        *m = emouse();
       +        } while ((m->buttons & bbit) && ecanmouse());
       +}
       +
       +
       +
       +/*********************************** Colors ***********************************/
       +
       +enum {        DOrange=0xffaa00FF, Dgray=0xbbbbbbFF, DDkgreen=0x009900FF,
       +        DDkred=0xcc0000FF, DViolet=0x990099FF, DDkyellow=0xaaaa00FF,
       +        DLtblue=0xaaaaffFF, DPink=0xffaaaaFF,
       +        /* ndraw.h sets DBlack, DBlue, DRed, DYellow, DGreen,
       +                DCyan, DMagenta, DWhite */
       +};
       +
       +typedef struct color_ref {
       +        ulong c;                        /* RGBA pixel color */
       +        char* nam;                        /* ASCII name (matched to input, used in output)*/
       +        Image* im;                        /* replicated solid-color image */
       +} color_ref;
       +
       +color_ref clrtab[] = {
       +        DRed,                "Red",                0,
       +        DPink,                "Pink",                0,
       +        DDkred,                "Dkred",        0,
       +        DOrange,        "Orange",        0,
       +        DYellow,        "Yellow",        0,
       +        DDkyellow,        "Dkyellow",        0,
       +        DGreen,                "Green",        0,
       +        DDkgreen,        "Dkgreen",        0,
       +        DCyan,                "Cyan",                0,
       +        DBlue,                "Blue",                0,
       +        DLtblue,        "Ltblue",        0,
       +        DMagenta,        "Magenta",        0,
       +        DViolet,        "Violet",        0,
       +        Dgray,                "Gray",                0,
       +        DBlack,                "Black",        0,
       +        DWhite,                "White",        0,
       +        DNofill,        0,                0        /* DNofill means "end of data" */
       +};
       +
       +
       +void  init_clrtab(void)
       +{
       +        int i;
       +        Rectangle r = Rect(0,0,1,1);
       +        for (i=0; clrtab[i].c!=DNofill; i++)
       +                clrtab[i].im = allocimage(display, r, CMAP8, 1, clrtab[i].c);
       +                /* should check for 0 result? */
       +}
       +
       +
       +int clrim_id(Image* clr)
       +{
       +        int i;
       +        for (i=0; clrtab[i].im!=clr; i++)
       +                if (clrtab[i].c==DNofill)
       +                        exits("bad image color");
       +        return i;
       +}
       +
       +int clr_id(int clr)
       +{
       +        int i;
       +        for (i=0; clrtab[i].c!=clr; i++)
       +                if (clrtab[i].c==DNofill)
       +                        exits("bad color");
       +        return i;
       +}
       +
       +#define clr_im(clr)        clrtab[clr_id(clr)].im
       +
       +
       +/* This decides what color to use for a polyline based on the label it has in the
       +   input file.  Whichever color name comes first is the winner, otherwise return black.
       +*/
       +Image* nam2clr(const char* nam, int *idxdest)
       +{
       +        char *c, *cbest=(char*)nam;
       +        int i, ibest=-1;
       +        if (*nam!=0)
       +                for (i=0; clrtab[i].nam!=0; i++) {
       +                        c = strstr(nam,clrtab[i].nam);
       +                        if (c!=0 && (ibest<0 || c<cbest))
       +                                {ibest=i; cbest=c;}
       +                }
       +        if (idxdest!=0)
       +                *idxdest = (ibest<0) ? clr_id(DBlack) : ibest;
       +        return (ibest<0) ? clr_im(DBlack) : clrtab[ibest].im;
       +}
       +
       +/* A polyline is initial drawn in thick mode iff its label in the file contains "Thick" */
       +int nam2thick(const char* nam)
       +{
       +        return strstr(nam,"Thick")==0 ? 0 : 1;
       +}
       +
       +
       +/* Alter string nam so that nam2thick() and nam2clr() agree with th and clr, using
       +   buf[] (a buffer of length bufn) to store the result if it differs from nam.
       +   We go to great pains to perform this alteration in a manner that will seem natural
       +   to the user, i.e., we try removing a suitably isolated color name before inserting
       +   a new one.
       +*/
       +char* nam_with_thclr(char* nam, int th, Image* clr, char* buf, int bufn)
       +{
       +        int clr0i, th0=nam2thick(nam);
       +        Image* clr0 = nam2clr(nam, &clr0i);
       +        char *clr0s;
       +        if (th0==th && clr0==clr)
       +                return nam;
       +        clr0s = clrtab[clr0i].nam;
       +        if (strlen(nam)<bufn) strcpy(buf,nam);
       +        else {strncpy(buf,nam,bufn); buf[bufn-1]='\0';}
       +        if (clr0 != clr)
       +                remove_substr(buf, clr0s);
       +        if (th0 > th)
       +                while (remove_substr(buf, "Thick"))
       +                        /* do nothing */;
       +        if (nam2clr(buf,0) != clr)
       +                str_insert(buf, clrtab[clrim_id(clr)].nam, bufn);
       +        if (th0 < th)
       +                str_insert(buf, "Thick", bufn);
       +        return buf;
       +}
       +
       +
       +
       +/****************************** Data structures  ******************************/
       +
       +Image* mv_bkgd;                                /* Background image (usually 0) */
       +
       +typedef struct fpoint {
       +        double x, y;
       +} fpoint;
       +
       +typedef struct frectangle {
       +        fpoint min, max;
       +} frectangle;
       +
       +frectangle empty_frect = {1e30, 1e30, -1e30, -1e30};
       +
       +
       +/* When *r2 is transformed by y=y-x*slant, might it intersect *r1 ?
       +*/
       +int fintersects(const frectangle* r1, const frectangle* r2, double slant)
       +{
       +        double x2min=r2->min.x, x2max=r2->max.x;
       +        if (r1->max.x <= x2min || x2max <= r1->min.x)
       +                return 0;
       +        if (slant >=0)
       +                {x2min*=slant; x2max*=slant;}
       +        else        {double t=x2min*slant; x2min=x2max*slant; x2max=t;}
       +        return r1->max.y > r2->min.y-x2max && r2->max.y-x2min > r1->min.y;
       +}
       +
       +int fcontains(const frectangle* r, fpoint p)
       +{
       +        return r->min.x <=p.x && p.x<= r->max.x && r->min.y <=p.y && p.y<= r->max.y;
       +}
       +
       +
       +void grow_bb(frectangle* dest, const frectangle* r)
       +{
       +        if (r->min.x < dest->min.x) dest->min.x=r->min.x;
       +        if (r->min.y < dest->min.y) dest->min.y=r->min.y;
       +        if (r->max.x > dest->max.x) dest->max.x=r->max.x;
       +        if (r->max.y > dest->max.y) dest->max.y=r->max.y;
       +}
       +
       +
       +void slant_frect(frectangle *r, double sl)
       +{
       +        r->min.y += sl*r->min.x;
       +        r->max.y += sl*r->max.x;
       +}
       +
       +
       +fpoint fcenter(const frectangle* r)
       +{
       +        fpoint c;
       +        c.x = .5*(r->max.x + r->min.x);
       +        c.y = .5*(r->max.y + r->min.y);
       +        return c;
       +}
       +
       +
       +typedef struct fpolygon {
       +        fpoint* p;                        /* a malloc'ed array */
       +        int n;                                /* p[] has n elements: p[0..n] */
       +        frectangle bb;                        /* bounding box */
       +        char* nam;                        /* name of this polygon (malloc'ed) */
       +        int thick;                        /* use 1+2*thick pixel wide lines */
       +        Image* clr;                        /* Color to use when drawing this */
       +        struct fpolygon* link;
       +} fpolygon;
       +
       +typedef struct fpolygons {
       +        fpolygon* p;                        /* the head of a linked list */
       +        frectangle bb;                        /* overall bounding box */
       +        frectangle disp;                /* part being mapped onto screen->r */
       +        double slant_ht;                /* controls how disp is slanted */
       +} fpolygons;
       +
       +
       +fpolygons univ = {                        /* everything there is to display */
       +        0,
       +        1e30, 1e30, -1e30, -1e30,
       +        0, 0, 0, 0,
       +        2*1e30
       +};
       +
       +
       +void set_default_clrs(fpolygons* fps, fpolygon* fpstop)
       +{
       +        fpolygon* fp;
       +        for (fp=fps->p; fp!=0 && fp!=fpstop; fp=fp->link) {
       +                fp->clr = nam2clr(fp->nam,0);
       +                fp->thick = nam2thick(fp->nam);
       +        }
       +}
       +
       +
       +void fps_invert(fpolygons* fps)
       +{
       +        fpolygon *p, *r=0;
       +        for (p=fps->p; p!=0;) {
       +                fpolygon* q = p;
       +                p = p->link;
       +                q->link = r;
       +                r = q;
       +        }
       +        fps->p = r;
       +}
       +
       +
       +void fp_remove(fpolygons* fps, fpolygon* fp)
       +{
       +        fpolygon *q, **p = &fps->p;
       +        while (*p!=fp)
       +                if (*p==0)
       +                        return;
       +                else        p = &(*p)->link;
       +        *p = fp->link;
       +        fps->bb = empty_frect;
       +        for (q=fps->p; q!=0; q=q->link)
       +                grow_bb(&fps->bb, &q->bb);
       +}
       +
       +
       +/* The transform maps abstract fpoint coordinates (the ones used in the input)
       +   to the current screen coordinates.  The do_untransform() macros reverses this.
       +   If univ.slant_ht is not the height of univ.disp, the actual region in the
       +   abstract coordinates is a parallelogram inscribed in univ.disp with two
       +   vertical edges and two slanted slanted edges: slant_ht>0 means that the
       +   vertical edges have height slant_ht and the parallelogram touches the lower
       +   left and upper right corners of univ.disp; slant_ht<0 refers to a parallelogram
       +   of height -slant_ht that touches the other two corners of univ.disp.
       +   NOTE: the ytransform macro assumes that tr->sl times the x coordinate has
       +   already been subtracted from yy.
       +*/
       +typedef struct transform {
       +        double sl;
       +        fpoint o, sc;                /* (x,y):->(o.x+sc.x*x, o.y+sc.y*y+sl*x) */
       +} transform;
       +
       +#define do_transform(d,tr,s)        ((d)->x = (tr)->o.x + (tr)->sc.x*(s)->x,  \
       +                                (d)->y = (tr)->o.y + (tr)->sc.y*(s)->y    \
       +                                        + (tr)->sl*(s)->x)
       +#define do_untransform(d,tr,s)        ((d)->x = (.5+(s)->x-(tr)->o.x)/(tr)->sc.x,    \
       +                                (d)->y = (.5+(s)->y-(tr)->sl*(d)->x-(tr)->o.y) \
       +                                        /(tr)->sc.y)
       +#define xtransform(tr,xx)        ((tr)->o.x + (tr)->sc.x*(xx))
       +#define ytransform(tr,yy)        ((tr)->o.y + (tr)->sc.y*(yy))
       +#define dxuntransform(tr,xx)        ((xx)/(tr)->sc.x)
       +#define dyuntransform(tr,yy)        ((yy)/(tr)->sc.y)
       +
       +
       +transform cur_trans(void)
       +{
       +        transform t;
       +        Rectangle d = screen->r;
       +        const frectangle* s = &univ.disp;
       +        double sh = univ.slant_ht;
       +        d.min.x += lft_border;
       +        d.min.y += top_border;
       +        d.max.x -= rt_border;
       +        d.max.y -= bot_border;
       +        t.sc.x = (d.max.x - d.min.x)/(s->max.x - s->min.x);
       +        t.sc.y = -(d.max.y - d.min.y)/fabs(sh);
       +        if (sh > 0) {
       +                t.sl = -t.sc.y*(s->max.y-s->min.y-sh)/(s->max.x - s->min.x);
       +                t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->max.x;
       +        } else {
       +                t.sl = t.sc.y*(s->max.y-s->min.y+sh)/(s->max.x - s->min.x);
       +                t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->min.x;
       +        }
       +        t.o.x = d.min.x - t.sc.x*s->min.x;
       +        return t;
       +}
       +
       +
       +double u_slant_amt(fpolygons *u)
       +{
       +        double sh=u->slant_ht, dy=u->disp.max.y - u->disp.min.y;
       +        double dx = u->disp.max.x - u->disp.min.x;
       +        return (sh>0) ? (dy-sh)/dx : -(dy+sh)/dx;
       +}
       +
       +
       +/* Set *y0 and *y1 to the lower and upper bounds of the set of y-sl*x values that
       +   *u says to display, where sl is the amount of slant.
       +*/
       +double set_unslanted_y(fpolygons *u, double *y0, double *y1)
       +{
       +        double yy1, sl=u_slant_amt(u);
       +        if (u->slant_ht > 0) {
       +                *y0 = u->disp.min.y - sl*u->disp.min.x;
       +                yy1 = *y0 + u->slant_ht;
       +        } else {
       +                yy1 = u->disp.max.y - sl*u->disp.min.x;
       +                *y0 = yy1 + u->slant_ht;
       +        }
       +        if (y1 != 0)
       +                *y1 = yy1;
       +        return sl;
       +}
       +
       +
       +
       +
       +/*************************** The region to display ****************************/
       +
       +void nontrivial_interval(double *lo, double *hi)
       +{
       +        if (*lo >= *hi) {
       +                double mid = .5*(*lo + *hi);
       +                double tweak = 1e-6 + 1e-6*fabs(mid);
       +                *lo = mid - tweak;
       +                *hi = mid + tweak;
       +        }
       +}
       +
       +
       +void init_disp(void)
       +{
       +        double dw = (univ.bb.max.x - univ.bb.min.x)*underscan;
       +        double dh = (univ.bb.max.y - univ.bb.min.y)*underscan;
       +        univ.disp.min.x = univ.bb.min.x - dw;
       +        univ.disp.min.y = univ.bb.min.y - dh;
       +        univ.disp.max.x = univ.bb.max.x + dw;
       +        univ.disp.max.y = univ.bb.max.y + dh;
       +        nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
       +        nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
       +        univ.slant_ht = univ.disp.max.y - univ.disp.min.y;        /* means no slant */
       +}
       +
       +
       +void recenter_disp(Point c)
       +{
       +        transform tr = cur_trans();
       +        fpoint cc, off;
       +        do_untransform(&cc, &tr, &c);
       +        off.x = cc.x - .5*(univ.disp.min.x + univ.disp.max.x);
       +        off.y = cc.y - .5*(univ.disp.min.y + univ.disp.max.y);
       +        univ.disp.min.x += off.x;
       +        univ.disp.min.y += off.y;
       +        univ.disp.max.x += off.x;
       +        univ.disp.max.y += off.y;
       +}
       +
       +
       +/* Find the upper-left and lower-right corners of the bounding box of the
       +   parallelogram formed by untransforming the rectangle rminx, rminy, ... (given
       +   in screen coordinates), and return the height of the parallelogram (negated
       +   if it slopes downward).
       +*/
       +double untransform_corners(double rminx, double rminy, double rmaxx, double rmaxy,
       +                fpoint *ul, fpoint *lr)
       +{
       +        fpoint r_ur, r_ul, r_ll, r_lr;        /* corners of the given recangle */
       +        fpoint ur, ll;                        /* untransformed versions of r_ur, r_ll */
       +        transform tr = cur_trans();
       +        double ht;
       +        r_ur.x=rmaxx;  r_ur.y=rminy;
       +        r_ul.x=rminx;  r_ul.y=rminy;
       +        r_ll.x=rminx;  r_ll.y=rmaxy;
       +        r_lr.x=rmaxx;  r_lr.y=rmaxy;
       +        do_untransform(ul, &tr, &r_ul);
       +        do_untransform(lr, &tr, &r_lr);
       +        do_untransform(&ur, &tr, &r_ur);
       +        do_untransform(&ll, &tr, &r_ll);
       +        ht = ur.y - lr->y;
       +        if (ll.x < ul->x)
       +                ul->x = ll.x;
       +        if (ur.y > ul->y)
       +                ul->y = ur.y;
       +        else        ht = -ht;
       +        if (ur.x > lr->x)
       +                lr->x = ur.x;
       +        if (ll.y < lr->y)
       +                lr->y = ll.y;
       +        return ht;
       +}
       +
       +
       +void disp_dozoom(double rminx, double rminy, double rmaxx, double rmaxy)
       +{
       +        fpoint ul, lr;
       +        double sh = untransform_corners(rminx, rminy, rmaxx, rmaxy, &ul, &lr);
       +        if (ul.x==lr.x || ul.y==lr.y)
       +                return;
       +        univ.slant_ht = sh;
       +        univ.disp.min.x = ul.x;
       +        univ.disp.max.y = ul.y;
       +        univ.disp.max.x = lr.x;
       +        univ.disp.min.y = lr.y;
       +        nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
       +        nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
       +}
       +
       +
       +void disp_zoomin(Rectangle r)
       +{
       +        disp_dozoom(r.min.x, r.min.y, r.max.x, r.max.y);
       +}
       +
       +
       +void disp_zoomout(Rectangle r)
       +{
       +        double qminx, qminy, qmaxx, qmaxy;
       +        double scx, scy;
       +        Rectangle s = screen->r;
       +        if (r.min.x==r.max.x || r.min.y==r.max.y)
       +                return;
       +        s.min.x += lft_border;
       +        s.min.y += top_border;
       +        s.max.x -= rt_border;
       +        s.max.y -= bot_border;
       +        scx = (s.max.x - s.min.x)/(r.max.x - r.min.x);
       +        scy = (s.max.y - s.min.y)/(r.max.y - r.min.y);
       +        qminx = s.min.x + scx*(s.min.x - r.min.x);
       +        qmaxx = s.max.x + scx*(s.max.x - r.max.x);
       +        qminy = s.min.y + scy*(s.min.y - r.min.y);
       +        qmaxy = s.max.y + scy*(s.max.y - r.max.y);
       +        disp_dozoom(qminx, qminy, qmaxx, qmaxy);
       +}
       +
       +
       +void expand2(double* a, double* b, double f)
       +{
       +        double mid = .5*(*a + *b);
       +        *a = mid + f*(*a - mid);
       +        *b = mid + f*(*b - mid);
       +}
       +
       +void disp_squareup(void)
       +{
       +        double dx = univ.disp.max.x - univ.disp.min.x;
       +        double dy = univ.disp.max.y - univ.disp.min.y;
       +        dx /= screen->r.max.x - lft_border - screen->r.min.x - rt_border;
       +        dy /= screen->r.max.y - bot_border - screen->r.min.y - top_border;
       +        if (dx > dy)
       +                expand2(&univ.disp.min.y, &univ.disp.max.y, dx/dy);
       +        else        expand2(&univ.disp.min.x, &univ.disp.max.x, dy/dx);
       +        univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
       +}
       +
       +
       +/* Slant so that p and q appear at the same height on the screen and the
       +   screen contains the smallest possible superset of what its previous contents.
       +*/
       +void slant_disp(fpoint p, fpoint q)
       +{
       +        double yll, ylr, yul, yur;        /* corner y coords of displayed parallelogram */
       +        double sh, dy;
       +        if (p.x == q.x)
       +                return;
       +        sh = univ.slant_ht;
       +        if (sh > 0) {
       +                yll=yul=univ.disp.min.y;  yul+=sh;
       +                ylr=yur=univ.disp.max.y;  ylr-=sh;
       +        } else {
       +                yll=yul=univ.disp.max.y;  yll+=sh;
       +                ylr=yur=univ.disp.min.y;  yur-=sh;
       +        }
       +        dy = (univ.disp.max.x-univ.disp.min.x)*(q.y - p.y)/(q.x - p.x);
       +        dy -= ylr - yll;
       +        if (dy > 0)
       +                {yll-=dy; yur+=dy;}
       +        else        {yul-=dy; ylr+=dy;}
       +        if (ylr > yll) {
       +                univ.disp.min.y = yll;
       +                univ.disp.max.y = yur;
       +                univ.slant_ht = yur - ylr;
       +        } else {
       +                univ.disp.max.y = yul;
       +                univ.disp.min.y = ylr;
       +                univ.slant_ht = ylr - yur;
       +        }
       +}
       +
       +
       +
       +
       +/******************************** Ascii input  ********************************/
       +
       +void set_fbb(fpolygon* fp)
       +{
       +        fpoint lo=fp->p[0], hi=fp->p[0];
       +        const fpoint *q, *qtop;
       +        for (qtop=(q=fp->p)+fp->n; ++q<=qtop;) {
       +                if (q->x < lo.x) lo.x=q->x;
       +                if (q->y < lo.y) lo.y=q->y;
       +                if (q->x > hi.x) hi.x=q->x;
       +                if (q->y > hi.y) hi.y=q->y;
       +        }
       +        fp->bb.min = lo;
       +        fp->bb.max = hi;
       +}
       +
       +char* mystrdup(char* s)
       +{
       +        char *r, *t = strrchr(s,'"');
       +        if (t==0) {
       +                t = s + strlen(s);
       +                while (t>s && (t[-1]=='\n' || t[-1]=='\r'))
       +                        t--;
       +        }
       +        r = malloc(1+(t-s));
       +        memcpy(r, s, t-s);
       +        r[t-s] = 0;
       +        return r;
       +}
       +
       +int is_valid_label(char* lab)
       +{
       +        char* t;
       +        if (lab[0]=='"')
       +                return (t=strrchr(lab,'"'))!=0 && t!=lab && strspn(t+1," \t\r\n")==strlen(t+1);
       +        return strcspn(lab," \t")==strlen(lab);
       +}
       +
       +/* Read a polyline and update the number of lines read.  A zero result indicates bad
       +   syntax if *lineno increases; otherwise it indicates end of file.
       +*/
       +fpolygon* rd_fpoly(FILE* fin, int *lineno)
       +{
       +        char buf[256], junk[2];
       +        fpoint q;
       +        fpolygon* fp;
       +        int allocn;
       +        if (!fgets(buf,256,fin))
       +                return 0;
       +        (*lineno)++;
       +        if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2)
       +                return 0;
       +        fp = malloc(sizeof(fpolygon));
       +        allocn = 16;
       +        fp->p = malloc(allocn*sizeof(fpoint));
       +        fp->p[0] = q;
       +        fp->n = 0;
       +        fp->nam = "";
       +        fp->thick = 0;
       +        fp->clr = clr_im(DBlack);
       +        while (fgets(buf,256,fin)) {
       +                (*lineno)++;
       +                if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2) {
       +                        if (!is_valid_label(buf))
       +                                {free(fp->p); free(fp); return 0;}
       +                        fp->nam = (buf[0]=='"') ? buf+1 : buf;
       +                        break;
       +                }
       +                if (++(fp->n) == allocn)
       +                        fp->p = realloc(fp->p, (allocn<<=1)*sizeof(fpoint));
       +                fp->p[fp->n] = q;
       +        }
       +        fp->nam = mystrdup(fp->nam);
       +        set_fbb(fp);
       +        fp->link = 0;
       +        return fp;
       +}
       +
       +
       +/* Read input into *fps and return 0 or a line number where there's a syntax error */
       +int rd_fpolys(FILE* fin, fpolygons* fps)
       +{
       +        fpolygon *fp, *fp0=fps->p;
       +        int lineno=0, ok_upto=0;
       +        while ((fp=rd_fpoly(fin,&lineno)) != 0) {
       +                ok_upto = lineno;
       +                fp->link = fps->p;
       +                fps->p = fp;
       +                grow_bb(&fps->bb, &fp->bb);
       +        }
       +        set_default_clrs(fps, fp0);
       +        return (ok_upto==lineno) ? 0 : lineno;
       +}
       +
       +
       +/* Read input from file fnam and return an error line no., -1 for "can't open"
       +   or 0 for success.
       +*/
       +int doinput(char* fnam)
       +{
       +        FILE* fin = strcmp(fnam,"-")==0 ? stdin : fopen(fnam, "r");
       +        int errline_or0;
       +        if (fin==0)
       +                return -1;
       +        errline_or0 = rd_fpolys(fin, &univ);
       +        fclose(fin);
       +        return errline_or0;
       +}
       +
       +
       +
       +/******************************** Ascii output ********************************/
       +
       +fpolygon* fp_reverse(fpolygon* fp)
       +{
       +        fpolygon* r = 0;
       +        while (fp!=0) {
       +                fpolygon* q = fp->link;
       +                fp->link = r;
       +                r = fp;
       +                fp = q;
       +        }
       +        return r;
       +}
       +
       +void wr_fpoly(FILE* fout, const fpolygon* fp)
       +{
       +        char buf[256];
       +        int i;
       +        for (i=0; i<=fp->n; i++)
       +                fprintf(fout,"%.12g\t%.12g\n", fp->p[i].x, fp->p[i].y);
       +        fprintf(fout,"\"%s\"\n", nam_with_thclr(fp->nam, fp->thick, fp->clr, buf, 256));
       +}
       +
       +void wr_fpolys(FILE* fout, fpolygons* fps)
       +{
       +        fpolygon* fp;
       +        fps->p = fp_reverse(fps->p);
       +        for (fp=fps->p; fp!=0; fp=fp->link)
       +                wr_fpoly(fout, fp);
       +        fps->p = fp_reverse(fps->p);
       +}
       +
       +
       +int dooutput(char* fnam)
       +{
       +        FILE* fout = fopen(fnam, "w");
       +        if (fout==0)
       +                return 0;
       +        wr_fpolys(fout, &univ);
       +        fclose(fout);
       +        return 1;
       +}
       +
       +
       +
       +
       +/************************ Clipping to screen rectangle ************************/
       +
       +/* Find the t values, 0<=t<=1 for which x0+t*(x1-x0) is between xlo and xhi,
       +   or return 0 to indicate no such t values exist.  If returning 1, set *t0 and
       +   *t1 to delimit the t interval.
       +*/
       +int do_xory(double x0, double x1, double xlo, double xhi, double* t0, double* t1)
       +{
       +        *t1 = 1.0;
       +        if (x0<xlo) {
       +                if (x1<xlo) return 0;
       +                *t0 = (xlo-x0)/(x1-x0);
       +                if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
       +        } else if (x0>xhi) {
       +                if (x1>xhi) return 0;
       +                *t0 = (xhi-x0)/(x1-x0);
       +                if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
       +        } else {
       +                *t0 = 0.0;
       +                if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
       +                else if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
       +                else *t1 = 1.0;
       +        }
       +        return 1;
       +}
       +
       +
       +/* After mapping y to y-slope*x, what initial fraction of the *p to *q edge is
       +   outside of *r?  Note that the edge could start outside *r, pass through *r,
       +   and wind up outside again.
       +*/
       +double frac_outside(const fpoint* p, const fpoint* q, const frectangle* r,
       +                double slope)
       +{
       +        double t0, t1, tt0, tt1;
       +        double px=p->x, qx=q->x;
       +        if (!do_xory(px, qx, r->min.x, r->max.x, &t0, &t1))
       +                return 1;
       +        if (!do_xory(p->y-slope*px, q->y-slope*qx, r->min.y, r->max.y, &tt0, &tt1))
       +                return 1;
       +        if (tt0 > t0)
       +                t0 = tt0;
       +        if (t1<=t0 || tt1<=t0)
       +                return 1;
       +        return t0;
       +}
       +
       +
       +/* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
       +   the maximum tt such that F(0..tt) is all inside of r, assuming p0 is inside.
       +   Coordinates are transformed by y=y-x*slope before testing against r.
       +*/
       +double in_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
       +{
       +        const fpoint* p = p0;
       +        double px, py;
       +        do if (++p > pn)
       +                return pn - p0;
       +        while (r.min.x<=(px=p->x) && px<=r.max.x
       +                        && r.min.y<=(py=p->y-slope*px) && py<=r.max.y);
       +        return (p - p0) - frac_outside(p, p-1, &r, slope);
       +}
       +
       +
       +/* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
       +   the maximum tt such that F(0..tt) is all outside of *r.  Coordinates are
       +   transformed by y=y-x*slope before testing against r.
       +*/
       +double out_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
       +{
       +        const fpoint* p = p0;
       +        double fr;
       +        do {        if (p->x < r.min.x)
       +                        do if (++p>pn) return pn-p0;
       +                        while (p->x <= r.min.x);
       +                else if (p->x > r.max.x)
       +                        do if (++p>pn) return pn-p0;
       +                        while (p->x >= r.max.x);
       +                else if (p->y-slope*p->x < r.min.y)
       +                        do if (++p>pn) return pn-p0;
       +                        while (p->y-slope*p->x <= r.min.y);
       +                else if (p->y-slope*p->x > r.max.y)
       +                        do if (++p>pn) return pn-p0;
       +                        while (p->y-slope*p->x >= r.max.y);
       +                else return p - p0;
       +        } while ((fr=frac_outside(p-1,p,&r,slope)) == 1);
       +        return (p - p0) + fr-1;
       +}
       +
       +
       +
       +/*********************** Drawing frame and axis labels  ***********************/
       +
       +#define Nthous  7
       +#define Len_thous  30                        /* bound on strlen(thous_nam[i]) */
       +char* thous_nam[Nthous] = {
       +        "one", "thousand", "million", "billion",
       +        "trillion", "quadrillion", "quintillion",
       +};
       +
       +
       +typedef struct lab_interval {
       +        double sep;                        /* separation between tick marks */
       +        double unit;                /* power of 1000 divisor */
       +        int logunit;                /* log base 1000 of of this divisor */
       +        double off;                        /* offset to subtract before dividing */
       +} lab_interval;
       +
       +
       +char* abbrev_num(double x, const lab_interval* iv)
       +{
       +        static char buf[16];
       +        double dx = x - iv->off;
       +        dx = iv->sep * floor(dx/iv->sep + .5);
       +        sprintf(buf,"%g", dx/iv->unit);
       +        return buf;
       +}
       +
       +
       +double lead_digits(double n, double r)        /* n truncated to power of 10 above r */
       +{
       +        double rr = pow(10, ceil(log10(r)));
       +        double nn = (n<rr) ? 0.0 : rr*floor(n/rr);
       +        if (n+r-nn >= digs10pow) {
       +                rr /= 10;
       +                nn = (n<rr) ? 0.0 : rr*floor(n/rr);
       +        }
       +        return nn;
       +}
       +
       +
       +lab_interval next_larger(double s0, double xlo, double xhi)
       +{
       +        double nlo, nhi;
       +        lab_interval r;
       +        r.logunit = (int) floor(log10(s0) + LOG2);
       +        r.unit = pow(10, r.logunit);
       +        nlo = xlo/r.unit;
       +        nhi = xhi/r.unit;
       +        if (nhi >= digs10pow)
       +                r.off = r.unit*lead_digits(nlo, nhi-nlo);
       +        else if (nlo <= -digs10pow)
       +                r.off = -r.unit*lead_digits(-nhi, nhi-nlo);
       +        else        r.off = 0;
       +        r.sep = (s0<=r.unit) ? r.unit : (s0<2*r.unit ? 2*r.unit : 5*r.unit);
       +        switch (r.logunit%3) {
       +        case 1:        r.unit*=.1; r.logunit--;
       +                break;
       +        case -1: case 2:
       +                r.unit*=10; r.logunit++;
       +                break;
       +        case -2: r.unit*=100; r.logunit+=2;
       +        }
       +        r.logunit /= 3;
       +        return r;
       +}
       +
       +
       +double min_hsep(const transform* tr)
       +{
       +        double s = (2+labdigs)*sdigit.x;
       +        double ss = (univ.disp.min.x<0) ? s+sdigit.x : s;
       +        return dxuntransform(tr, ss);
       +}
       +
       +
       +lab_interval mark_x_axis(const transform* tr)
       +{
       +        fpoint p = univ.disp.min;
       +        Point q, qtop, qbot, tmp;
       +        double x0=univ.disp.min.x, x1=univ.disp.max.x;
       +        double seps0, nseps, seps;
       +        lab_interval iv = next_larger(min_hsep(tr), x0, x1);
       +        set_unslanted_y(&univ, &p.y, 0);
       +        q.y = ytransform(tr, p.y) + .5;
       +        qtop.y = q.y - tick_len;
       +        qbot.y = q.y + framewd + framesep;
       +        seps0 = ceil(x0/iv.sep);
       +        for (seps=0, nseps=floor(x1/iv.sep)-seps0; seps<=nseps; seps+=1) {
       +                char* num = abbrev_num((p.x=iv.sep*(seps0+seps)), &iv);
       +                Font* f = display->defaultfont;
       +                q.x = qtop.x = qbot.x = xtransform(tr, p.x);
       +                line(screen, qtop, q, Enddisc, Enddisc, 0, axis_color, q);
       +                tmp = stringsize(f, num);
       +                qbot.x -= tmp.x/2;
       +                string(screen, qbot, display->black, qbot, f, num);
       +        }
       +        return iv;
       +}
       +
       +
       +lab_interval mark_y_axis(const transform* tr)
       +{
       +        Font* f = display->defaultfont;
       +        fpoint p = univ.disp.min;
       +        Point q, qrt, qlft;
       +        double y0, y1, seps0, nseps, seps;
       +        lab_interval iv;
       +        set_unslanted_y(&univ, &y0, &y1);
       +        iv = next_larger(dyuntransform(tr,-f->height), y0, y1);
       +        q.x = xtransform(tr, p.x) - .5;
       +        qrt.x = q.x + tick_len;
       +        qlft.x = q.x - (framewd + framesep);
       +        seps0 = ceil(y0/iv.sep);
       +        for (seps=0, nseps=floor(y1/iv.sep)-seps0; seps<=nseps; seps+=1) {
       +                char* num = abbrev_num((p.y=iv.sep*(seps0+seps)), &iv);
       +                Point qq = stringsize(f, num);
       +                q.y = qrt.y = qlft.y = ytransform(tr, p.y);
       +                line(screen, qrt, q, Enddisc, Enddisc, 0, axis_color, q);
       +                qq.x = qlft.x - qq.x;
       +                qq.y = qlft.y - qq.y/2;
       +                string(screen, qq, display->black, qq, f, num);
       +        }
       +        return iv;
       +}
       +
       +
       +void lab_iv_info(const lab_interval *iv, double slant, char* buf, int *n)
       +{
       +        if (iv->off > 0)
       +                (*n) += sprintf(buf+*n,"-%.12g",iv->off);
       +        else if (iv->off < 0)
       +                (*n) += sprintf(buf+*n,"+%.12g",-iv->off);
       +        if (slant>0)
       +                (*n) += sprintf(buf+*n,"-%.6gx", slant);
       +        else if (slant<0)
       +                (*n) += sprintf(buf+*n,"+%.6gx", -slant);
       +        if (abs(iv->logunit) >= Nthous)
       +                (*n) += sprintf(buf+*n," in 1e%d units", 3*iv->logunit);
       +        else if (iv->logunit > 0)
       +                (*n) += sprintf(buf+*n," in %ss", thous_nam[iv->logunit]);
       +        else if (iv->logunit < 0)
       +                (*n) += sprintf(buf+*n," in %sths", thous_nam[-iv->logunit]);
       +}
       +
       +
       +void draw_xy_ranges(const lab_interval *xiv, const lab_interval *yiv)
       +{
       +        Point p;
       +        char buf[2*(19+Len_thous+8)+50];
       +        int bufn = 0;
       +        buf[bufn++] = 'x';
       +        lab_iv_info(xiv, 0, buf, &bufn);
       +        bufn += sprintf(buf+bufn, "; y");
       +        lab_iv_info(yiv, u_slant_amt(&univ), buf, &bufn);
       +        buf[bufn] = '\0';
       +        p = stringsize(display->defaultfont, buf);
       +        top_left = screen->r.min.x + lft_border;
       +        p.x = top_right = screen->r.max.x - rt_border - p.x;
       +        p.y = screen->r.min.y + outersep;
       +        string(screen, p, display->black, p, display->defaultfont, buf);
       +}
       +
       +
       +transform draw_frame(void)
       +{
       +        lab_interval x_iv, y_iv;
       +        transform tr;
       +        Rectangle r = screen->r;
       +        lft_border = (univ.disp.min.y<0) ? lft_border0+sdigit.x : lft_border0;
       +        tr = cur_trans();
       +        r.min.x += lft_border;
       +        r.min.y += top_border;
       +        r.max.x -= rt_border;
       +        r.max.y -= bot_border;
       +        border(screen, r, -framewd, axis_color, r.min);
       +        x_iv = mark_x_axis(&tr);
       +        y_iv = mark_y_axis(&tr);
       +        draw_xy_ranges(&x_iv, &y_iv);
       +        return tr;
       +}
       +
       +
       +
       +/*************************** Finding the selection  ***************************/
       +
       +typedef struct pt_on_fpoly {
       +        fpoint p;                        /* the point */
       +        fpolygon* fp;                        /* the fpolygon it lies on */
       +        double t;                        /* how many knots from the beginning */
       +} pt_on_fpoly;
       +
       +
       +static double myx, myy;
       +#define mydist(p,o,sl,xwt,ywt)        (myx=(p).x-(o).x, myy=(p).y-sl*(p).x-(o).y,        \
       +                                        xwt*myx*myx + ywt*myy*myy)
       +
       +/* At what fraction of the way from p0[0] to p0[1] is mydist(p,ctr,slant,xwt,ywt)
       +   minimized?
       +*/
       +double closest_time(const fpoint* p0, const fpoint* ctr, double slant,
       +                double xwt, double ywt)
       +{
       +        double p00y=p0[0].y-slant*p0[0].x, p01y=p0[1].y-slant*p0[1].x;
       +        double dx=p0[1].x-p0[0].x, dy=p01y-p00y;
       +        double x0=p0[0].x-ctr->x, y0=p00y-ctr->y;
       +        double bot = xwt*dx*dx + ywt*dy*dy;
       +        if (bot==0)
       +                return 0;
       +        return -(xwt*x0*dx + ywt*y0*dy)/bot;
       +}
       +
       +
       +/* Scan the polygonal path of length len knots starting at p0, and find the
       +   point that the transformation y=y-x*slant makes closest to the center of *r,
       +   where *r itself defines the distance metric.  Knots get higher priority than
       +   points between knots.  If psel->t is negative, always update *psel; otherwise
       +   update *psel only if the scan can improve it.  Return a boolean that says
       +   whether *psel was updated.
       +     Note that *r is a very tiny rectangle (tiny when converted screen pixels)
       +   such that anything in *r is considered close enough to match the mouse click.
       +   The purpose of this routine is to be careful in case there is a lot of hidden
       +   detail in the tiny rectangle *r.
       +*/
       +int improve_pt(fpoint* p0, double len, const frectangle* r, double slant,
       +                pt_on_fpoly* psel)
       +{
       +        fpoint ctr = fcenter(r);
       +        double x_wt=2/(r->max.x-r->min.x), y_wt=2/(r->max.y-r->min.y);
       +        double xwt=x_wt*x_wt, ywt=y_wt*y_wt;
       +        double d, dbest = (psel->t <0) ? 1e30 : mydist(psel->p,ctr,slant,xwt,ywt);
       +        double tt, dbest0 = dbest;
       +        fpoint pp;
       +        int ilen = (int) len;
       +        if (len==0 || ilen>0) {
       +                int i;
       +                for (i=(len==0 ? 0 : 1); i<=ilen; i++) {
       +                        d = mydist(p0[i], ctr, slant, xwt, ywt);
       +                        if (d < dbest)
       +                                {psel->p=p0[i]; psel->t=i; dbest=d;}
       +                }
       +                return (dbest < dbest0);
       +        }
       +        tt = closest_time(p0, &ctr, slant, xwt, ywt);
       +        if (tt > len)
       +                tt = len;
       +        pp.x = p0[0].x + tt*(p0[1].x - p0[0].x);
       +        pp.y = p0[0].y + tt*(p0[1].y - p0[0].y);
       +        if (mydist(pp, ctr, slant, xwt, ywt) < dbest) {
       +                psel->p = pp;
       +                psel->t = tt;
       +                return 1;
       +        }
       +        return 0;
       +}
       +
       +
       +/* Test *fp against *r after transforming by y=y-x*slope, and set *psel accordingly.
       +*/
       +void select_in_fpoly(fpolygon* fp, const frectangle* r, double slant,
       +                pt_on_fpoly* psel)
       +{
       +        fpoint *p0=fp->p, *pn=fp->p+fp->n;
       +        double l1, l2;
       +        if (p0==pn)
       +                {improve_pt(p0, 0, r, slant, psel); psel->fp=fp; return;}
       +        while ((l1=out_length(p0,pn,*r,slant)) < pn-p0) {
       +                fpoint p0sav;
       +                int i1 = (int) l1;
       +                p0+=i1; l1-=i1;
       +                p0sav = *p0;
       +                p0[0].x += l1*(p0[1].x - p0[0].x);
       +                p0[0].y += l1*(p0[1].y - p0[0].y);
       +                l2 = in_length(p0, pn, *r, slant);
       +                if (improve_pt(p0, l2, r, slant, psel)) {
       +                        if (l1==0 && psel->t!=((int) psel->t)) {
       +                                psel->t = 0;
       +                                psel->p = *p0;
       +                        } else if (psel->t < 1)
       +                                psel->t += l1*(1 - psel->t);
       +                        psel->t += p0 - fp->p;
       +                        psel->fp = fp;
       +                }
       +                *p0 = p0sav;
       +                p0 += (l2>0) ? ((int) ceil(l2)) : 1;
       +        }
       +}
       +
       +
       +/* Test all the fpolygons against *r after transforming by y=y-x*slope, and return
       +   the resulting selection, if any.
       +*/
       +pt_on_fpoly* select_in_univ(const frectangle* r, double slant)
       +{
       +        static pt_on_fpoly answ;
       +        fpolygon* fp;
       +        answ.t = -1;
       +        for (fp=univ.p; fp!=0; fp=fp->link)
       +                if (fintersects(r, &fp->bb, slant))
       +                        select_in_fpoly(fp, r, slant, &answ);
       +        if (answ.t < 0)
       +                return 0;
       +        return &answ;
       +}
       +
       +
       +
       +/**************************** Using the selection  ****************************/
       +
       +pt_on_fpoly cur_sel;                        /* current selection if cur_sel.t>=0 */
       +pt_on_fpoly prev_sel;                        /* previous selection if prev_sel.t>=0 (for slant) */
       +Image* sel_bkg = 0;                        /* what's behind the red dot */
       +
       +
       +void clear_txt(void)
       +{
       +        Rectangle r;
       +        r.min = screen->r.min;
       +        r.min.x += lft_border;
       +        r.min.y += outersep;
       +        r.max.x = top_left;
       +        r.max.y = r.min.y + smaxch.y;
       +        draw(screen, r, display->white, display->opaque, r.min);
       +        top_left = r.min.x;
       +}
       +
       +
       +Rectangle sel_dot_box(const transform* tr)
       +{
       +        Point ctr;
       +        Rectangle r;
       +        if (tr==0)
       +                ctr.x = ctr.y = Dotrad;
       +        else        do_transform(&ctr, tr, &cur_sel.p);
       +        r.min.x=ctr.x-Dotrad;  r.max.x=ctr.x+Dotrad+1;
       +        r.min.y=ctr.y-Dotrad;  r.max.y=ctr.y+Dotrad+1;
       +        return r;
       +}
       +
       +
       +void unselect(const transform* tr)
       +{
       +        transform tra;
       +        if (sel_bkg==0)
       +                sel_bkg = allocimage(display, sel_dot_box(0), CMAP8, 0, DWhite);
       +        clear_txt();
       +        if (cur_sel.t < 0)
       +                return;
       +        prev_sel = cur_sel;
       +        if (tr==0)
       +                {tra=cur_trans(); tr=&tra;}
       +        draw(screen, sel_dot_box(tr), sel_bkg, display->opaque, ZP);
       +        cur_sel.t = -1;
       +}
       +
       +
       +/* Text at top right is written first and this low-level routine clobbers it if
       +   the new top-left text would overwrite it.  However, users of this routine should
       +   try to keep the new text short enough to avoid this.
       +*/
       +void show_mytext(char* msg)
       +{
       +        Point tmp, pt = screen->r.min;
       +        int siz;
       +        tmp = stringsize(display->defaultfont, msg);
       +        siz = tmp.x;
       +        pt.x=top_left;  pt.y+=outersep;
       +        if (top_left+siz > top_right) {
       +                Rectangle r;
       +                r.min.y = pt.y;
       +                r.min.x = top_right;
       +                r.max.y = r.min.y + smaxch.y;
       +                r.max.x = top_left+siz;
       +                draw(screen, r, display->white, display->opaque, r.min);
       +                top_right = top_left+siz;
       +        }
       +        string(screen, pt, display->black, ZP, display->defaultfont, msg);
       +        top_left += siz;
       +}
       +
       +
       +double rnd(double x, double tol)        /* round to enough digits for accuracy tol */
       +{
       +        double t = pow(10, floor(log10(tol)));
       +        return t * floor(x/t + .5);
       +}
       +
       +double t_tol(double xtol, double ytol)
       +{
       +        int t = (int) floor(cur_sel.t);
       +        fpoint* p = cur_sel.fp->p;
       +        double dx, dy;
       +        if (t==cur_sel.t)
       +                return 1;
       +        dx = fabs(p[t+1].x - p[t].x);
       +        dy = fabs(p[t+1].y - p[t].y);
       +        xtol /= (xtol>dx) ? xtol : dx;
       +        ytol /= (ytol>dy) ? ytol : dy;
       +        return (xtol<ytol) ? xtol : ytol;
       +}
       +
       +void say_where(const transform* tr)
       +{
       +        double xtol=dxuntransform(tr,1), ytol=dyuntransform(tr,-1);
       +        char buf[100];
       +        int n, nmax = (top_right - top_left)/smaxch.x;
       +        if (nmax >= 100)
       +                nmax = 100-1;
       +        n = sprintf(buf,"(%.14g,%.14g) at t=%.14g",
       +                        rnd(cur_sel.p.x,xtol), rnd(cur_sel.p.y,ytol),
       +                        rnd(cur_sel.t, t_tol(xtol,ytol)));
       +        if (cur_sel.fp->nam[0] != 0)
       +                sprintf(buf+n," %.*s", nmax-n-1, cur_sel.fp->nam);
       +        show_mytext(buf);
       +}
       +
       +
       +void reselect(const transform* tr)        /* uselect(); set cur_sel; call this */
       +{
       +        Point pt2, pt3;
       +        fpoint p2;
       +        transform tra;
       +        if (cur_sel.t < 0)
       +                return;
       +        if (tr==0)
       +                {tra=cur_trans(); tr=&tra;}
       +        do_transform(&p2, tr, &cur_sel.p);
       +        if (fabs(p2.x)+fabs(p2.y)>1e8 || (pt2.x=p2.x, pt2.y=p2.y, is_off_screen(pt2)))
       +                {cur_sel.t= -1; return;}
       +        pt3.x=pt2.x-Dotrad;  pt3.y=pt2.y-Dotrad;
       +        draw(sel_bkg, sel_dot_box(0), screen, display->opaque, pt3);
       +        fillellipse(screen, pt2, Dotrad, Dotrad, clr_im(DRed), pt2);
       +        say_where(tr);
       +}
       +
       +
       +void do_select(Point pt)
       +{
       +        transform tr = cur_trans();
       +        fpoint pt1, pt2, ctr;
       +        frectangle r;
       +        double slant;
       +        pt_on_fpoly* psel;
       +        unselect(&tr);
       +        do_untransform(&ctr, &tr, &pt);
       +        pt1.x=pt.x-fuzz;  pt1.y=pt.y+fuzz;
       +        pt2.x=pt.x+fuzz;  pt2.y=pt.y-fuzz;
       +        do_untransform(&r.min, &tr, &pt1);
       +        do_untransform(&r.max, &tr, &pt2);
       +        slant = u_slant_amt(&univ);
       +        slant_frect(&r, -slant);
       +        psel = select_in_univ(&r, slant);
       +        if (psel==0)
       +                return;
       +        if (logfil!=0) {
       +                fprintf(logfil,"%.14g\t%.14g\n", psel->p.x, psel->p.y);
       +                fflush(logfil);
       +        }
       +        cur_sel = *psel;
       +        reselect(&tr);
       +}
       +
       +
       +/***************************** Prompting for text *****************************/
       +
       +void unshow_mytext(char* msg)
       +{
       +        Rectangle r;
       +        Point siz = stringsize(display->defaultfont, msg);
       +        top_left -= siz.x;
       +        r.min.y = screen->r.min.y + outersep;
       +        r.min.x = top_left;
       +        r.max.y = r.min.y + siz.y;
       +        r.max.x = r.min.x + siz.x;
       +        draw(screen, r, display->white, display->opaque, r.min);
       +}
       +
       +
       +/* Show the given prompt and read a line of user input.  The text appears at the
       +   top left.  If it runs into the top right text, we stop echoing but let the user
       +   continue typing blind if he wants to.
       +*/
       +char* prompt_text(char* prompt)
       +{
       +        static char buf[200];
       +        int n0, n=0, nshown=0;
       +        Rune c;
       +        unselect(0);
       +        show_mytext(prompt);
       +        while (n<200-1-UTFmax && (c=ekbd())!='\n') {
       +                if (c=='\b') {
       +                        buf[n] = 0;
       +                        if (n > 0)
       +                                do n--;
       +                                while (n>0 && (buf[n-1]&0xc0)==0x80);
       +                        if (n < nshown)
       +                                {unshow_mytext(buf+n); nshown=n;}
       +                } else {
       +                        n0 = n;
       +                        n += runetochar(buf+n, &c);
       +                        buf[n] = 0;
       +                        if (nshown==n0 && top_right-top_left >= smaxch.x)
       +                                {show_mytext(buf+n0); nshown=n;}
       +                }
       +        }
       +        buf[n] = 0;
       +        while (ecanmouse())
       +                emouse();
       +        return buf;
       +}
       +
       +
       +/**************************** Redrawing the screen ****************************/
       +
       +/* Let p0 and its successors define a piecewise-linear function of a paramter t,
       +   and draw the 0<=t<=n1 portion using transform *tr.
       +*/
       +void draw_fpts(const fpoint* p0, double n1, const transform* tr, int thick,
       +                Image* clr)
       +{
       +        int n = (int) n1;
       +        const fpoint* p = p0 + n;
       +        fpoint pp;
       +        Point qq, q;
       +        if (n1 > n) {
       +                pp.x = p[0].x + (n1-n)*(p[1].x - p[0].x);
       +                pp.y = p[0].y + (n1-n)*(p[1].y - p[0].y);
       +        } else        pp = *p--;
       +        do_transform(&qq, tr, &pp);
       +        if (n1==0)
       +                fillellipse(screen, qq, 1+thick, 1+thick, clr, qq);
       +        for (; p>=p0; p--) {
       +                do_transform(&q, tr, p);
       +                line(screen, qq, q, Enddisc, Enddisc, thick, clr, qq);
       +                qq = q;
       +        }
       +}
       +
       +void draw_1fpoly(const fpolygon* fp, const transform* tr, Image* clr,
       +                const frectangle *udisp, double slant)
       +{
       +        fpoint *p0=fp->p, *pn=fp->p+fp->n;
       +        double l1, l2;
       +        if (p0==pn && fcontains(udisp,*p0))
       +                {draw_fpts(p0, 0, tr, fp->thick, clr); return;}
       +        while ((l1=out_length(p0,pn,*udisp,slant)) < pn-p0) {
       +                fpoint p0sav;
       +                int i1 = (int) l1;
       +                p0+=i1; l1-=i1;
       +                p0sav = *p0;
       +                p0[0].x += l1*(p0[1].x - p0[0].x);
       +                p0[0].y += l1*(p0[1].y - p0[0].y);
       +                l2 = in_length(p0, pn, *udisp, slant);
       +                draw_fpts(p0, l2, tr, fp->thick, clr);
       +                *p0 = p0sav;
       +                p0 += (l2>0) ? ((int) ceil(l2)) : 1;
       +        }
       +}
       +
       +
       +double get_clip_data(const fpolygons *u, frectangle *r)
       +{
       +        double slant = set_unslanted_y((fpolygons*)u, &r->min.y, &r->max.y);
       +        r->min.x = u->disp.min.x;
       +        r->max.x = u->disp.max.x;
       +        return slant;
       +}
       +
       +
       +void draw_fpoly(const fpolygon* fp, const transform* tr, Image* clr)
       +{
       +        frectangle r;
       +        double slant = get_clip_data(&univ, &r);
       +        draw_1fpoly(fp, tr, clr, &r, slant);
       +}
       +
       +
       +void eresized(int new)
       +{
       +        transform tr;
       +        fpolygon* fp;
       +        frectangle clipr;
       +        double slant;
       +        if(new && getwindow(display, Refmesg) < 0) {
       +                fprintf(stderr,"can't reattach to window\n");
       +                exits("reshap");
       +        }
       +        draw(screen, screen->r, display->white, display->opaque, screen->r.min);
       +        tr = draw_frame();
       +        slant = get_clip_data(&univ, &clipr);
       +        for (fp=univ.p; fp!=0; fp=fp->link)
       +                if (fintersects(&clipr, &fp->bb, slant))
       +                        draw_1fpoly(fp, &tr, fp->clr, &clipr, slant);
       +        reselect(0);
       +        if (mv_bkgd!=0 && mv_bkgd->repl==0) {
       +                freeimage(mv_bkgd);
       +                mv_bkgd = display->white;
       +        }
       +        flushimage(display, 1);
       +}
       +
       +
       +
       +
       +/********************************* Recoloring *********************************/
       +
       +int draw_palette(int n)                /* n is number of colors; returns patch dy */
       +{
       +        int y0 = screen->r.min.y + top_border;
       +        int dy = (screen->r.max.y - bot_border - y0)/n;
       +        Rectangle r;
       +        int i;
       +        r.min.y = y0;
       +        r.min.x = screen->r.max.x - rt_border + framewd;
       +        r.max.y = y0 + dy;
       +        r.max.x = screen->r.max.x;
       +        for (i=0; i<n; i++) {
       +                draw(screen, r, clrtab[i].im, display->opaque, r.min);
       +                r.min.y = r.max.y;
       +                r.max.y += dy;
       +        }
       +        return dy;
       +}
       +
       +
       +Image* palette_color(Point pt, int dy, int n)
       +{                                /* mouse at pt, patch size dy, n colors */
       +        int yy;
       +        if (screen->r.max.x - pt.x > rt_border - framewd)
       +                return 0;
       +        yy = pt.y - (screen->r.min.y + top_border);
       +        if (yy<0 || yy>=n*dy)
       +                return 0;
       +        return clrtab[yy/dy].im;
       +}
       +
       +
       +void all_set_clr(fpolygons* fps, Image* clr)
       +{
       +        fpolygon* p;
       +        for (p=fps->p; p!=0; p=p->link)
       +                p->clr = clr;
       +}
       +        
       +
       +void do_recolor(int but, Mouse* m, int alluniv)
       +{
       +        int nclr = clr_id(DWhite);
       +        int dy = draw_palette(nclr);
       +        Image* clr;
       +        if (!get_1click(but, m, 0)) {
       +                eresized(0);
       +                return;
       +        }
       +        clr = palette_color(m->xy, dy, nclr);
       +        if (clr != 0) {
       +                if (alluniv)
       +                        all_set_clr(&univ, clr);
       +                else        cur_sel.fp->clr = clr;
       +        }
       +        eresized(0);
       +        lift_button(but, m, Never);
       +}
       +
       +
       +/****************************** Move and rotate  ******************************/
       +
       +void prepare_mv(const fpolygon* fp)
       +{
       +        Rectangle r = screen->r;
       +        Image* scr0;
       +        int dt = 1 + fp->thick;
       +        r.min.x+=lft_border-dt;  r.min.y+=top_border-dt;
       +        r.max.x-=rt_border-dt;   r.max.y-=bot_border-dt;
       +        if (mv_bkgd!=0 && mv_bkgd->repl==0)
       +                freeimage(mv_bkgd);
       +        mv_bkgd = allocimage(display, r, CMAP8, 0, DNofill);
       +        if (mv_bkgd==0)
       +                mv_bkgd = display->white;
       +        else {        transform tr = cur_trans();
       +                draw(mv_bkgd, r, screen, display->opaque, r.min);
       +                draw(mv_bkgd, sel_dot_box(&tr), sel_bkg, display->opaque, ZP);
       +                scr0 = screen;
       +                screen = mv_bkgd;
       +                draw_fpoly(fp, &tr, display->white);
       +                screen = scr0;
       +        }
       +}
       +
       +
       +void move_fp(fpolygon* fp, double dx, double dy)
       +{
       +        fpoint *p, *pn=fp->p+fp->n;
       +        for (p=fp->p; p<=pn; p++) {
       +                (p->x) += dx;
       +                (p->y) += dy;
       +        }
       +        (fp->bb.min.x)+=dx;  (fp->bb.min.y)+=dy;
       +        (fp->bb.max.x)+=dx;  (fp->bb.max.y)+=dy;
       +}
       +
       +
       +void rotate_fp(fpolygon* fp, fpoint o, double theta)
       +{
       +        double s=sin(theta), c=cos(theta);
       +        fpoint *p, *pn=fp->p+fp->n;
       +        for (p=fp->p; p<=pn; p++) {
       +                double x=p->x-o.x, y=p->y-o.y;
       +                (p->x) = o.x + c*x - s*y;
       +                (p->y) = o.y + s*x + c*y;
       +        }
       +        set_fbb(fp);
       +}
       +
       +
       +/* Move the selected fpolygon so the selected point tracks the mouse, and return
       +   the total amount of movement.  Button but has already been held down for at
       +   least Mv_delay milliseconds and the mouse might have moved some distance.
       +*/
       +fpoint do_move(int but, Mouse* m)
       +{
       +        transform tr = cur_trans();
       +        int bbit = Button_bit(but);
       +        fpolygon* fp = cur_sel.fp;
       +        fpoint loc, loc0=cur_sel.p;
       +        double tsav = cur_sel.t;
       +        unselect(&tr);
       +        do {        latest_mouse(but, m);
       +                (fp->thick)++;                /* line() DISAGREES WITH ITSELF */
       +                draw_fpoly(fp, &tr, mv_bkgd);
       +                (fp->thick)--;
       +                do_untransform(&loc, &tr, &m->xy);
       +                move_fp(fp, loc.x-cur_sel.p.x, loc.y-cur_sel.p.y);
       +                cur_sel.p = loc;
       +                draw_fpoly(fp, &tr, fp->clr);
       +        } while (m->buttons & bbit);
       +        cur_sel.t = tsav;
       +        reselect(&tr);
       +        loc.x -= loc0.x;
       +        loc.y -= loc0.y;
       +        return loc;
       +}
       +
       +
       +double dir_angle(const Point* pt, const transform* tr)
       +{
       +        fpoint p;
       +        double dy, dx;
       +        do_untransform(&p, tr, pt);
       +        dy=p.y-cur_sel.p.y;  dx=p.x-cur_sel.p.x;
       +        return (dx==0 && dy==0) ? 0.0 : atan2(dy, dx);
       +}
       +
       +
       +/* Rotate the selected fpolygon around the selection point so as to track the
       +   direction angle from the selected point to m->xy.  Stop when button but goes
       +   up and return the total amount of rotation in radians.
       +*/
       +double do_rotate(int but, Mouse* m)
       +{
       +        transform tr = cur_trans();
       +        int bbit = Button_bit(but);
       +        fpolygon* fp = cur_sel.fp;
       +        double theta0 = dir_angle(&m->xy, &tr);
       +        double th, theta = theta0;
       +        do {        latest_mouse(but, m);
       +                (fp->thick)++;                /* line() DISAGREES WITH ITSELF */
       +                draw_fpoly(fp, &tr, mv_bkgd);
       +                (fp->thick)--;
       +                th = dir_angle(&m->xy, &tr);
       +                rotate_fp(fp, cur_sel.p, th-theta);
       +                theta = th;
       +                draw_fpoly(fp, &tr, fp->clr);
       +        } while (m->buttons & bbit);
       +        unselect(&tr);
       +        cur_sel = prev_sel;
       +        reselect(&tr);
       +        return theta - theta0;
       +}
       +
       +
       +
       +/********************************* Edit menu  *********************************/
       +
       +typedef enum e_index {
       +                Erecolor, Ethick, Edelete, Eundo, Erotate, Eoptions,
       +                Emove
       +} e_index;
       +
       +char* e_items[Eoptions+1];
       +
       +Menu e_menu = {e_items, 0, 0};
       +
       +
       +typedef struct e_action {
       +        e_index typ;                        /* What type of action */
       +        fpolygon* fp;                        /* fpolygon the action applies to */
       +        Image* clr;                        /* color to use if typ==Erecolor */
       +        double amt;                        /* rotation angle or line thickness */
       +        fpoint pt;                        /* movement vector or rotation center */
       +        struct e_action* link;                /* next in a stack */
       +} e_action;
       +
       +e_action* unact = 0;                        /* heads a linked list of actions */
       +e_action* do_undo(e_action*);                /* pop off an e_action and (un)do it */
       +e_action* save_act(e_action*,e_index);        /* append new e_action for status quo */
       +
       +
       +void save_mv(fpoint movement)
       +{
       +        unact = save_act(unact, Emove);
       +        unact->pt = movement;
       +}
       +
       +
       +void init_e_menu(void)
       +{
       +        char* u = "can't undo";
       +        e_items[Erecolor] = "recolor";
       +        e_items[Edelete] = "delete";
       +        e_items[Erotate] = "rotate";
       +        e_items[Eoptions-cantmv] = 0;
       +        e_items[Ethick] = (cur_sel.fp->thick >0) ? "thin" : "thick";
       +        if (unact!=0)
       +                switch (unact->typ) {
       +                case Erecolor: u="uncolor"; break;
       +                case Ethick: u=(unact->fp->thick==0) ? "unthin" : "unthicken";
       +                        break;
       +                case Edelete: u="undelete"; break;
       +                case Emove: u="unmove"; break;
       +                case Erotate: u="unrotate"; break;
       +                }
       +        e_items[Eundo] = u;
       +}
       +
       +
       +void do_emenu(int but, Mouse* m)
       +{
       +        int h;
       +        if (cur_sel.t < 0)
       +                return;
       +        init_e_menu();
       +        h = emenuhit(but, m, &e_menu);
       +        switch(h) {
       +        case Ethick: unact = save_act(unact, h);
       +                cur_sel.fp->thick ^= 1;
       +                eresized(0);
       +                break;
       +        case Edelete: unact = save_act(unact, h);
       +                fp_remove(&univ, cur_sel.fp);
       +                unselect(0);
       +                eresized(0);
       +                break;
       +        case Erecolor: unact = save_act(unact, h);
       +                do_recolor(but, m, 0);
       +                break;
       +        case Erotate: unact = save_act(unact, h);
       +                prepare_mv(cur_sel.fp);
       +                if (get_1click(but, m, 0)) {
       +                        unact->pt = cur_sel.p;
       +                        unact->amt = do_rotate(but, m);
       +                }
       +                break;
       +        case Eundo: unact = do_undo(unact);
       +                break;
       +        }
       +}
       +
       +
       +
       +/******************************* Undoing edits  *******************************/
       +
       +e_action* save_act(e_action* a0, e_index typ)
       +{                                        /* append new e_action for status quo */
       +        e_action* a = malloc(sizeof(e_action));
       +        a->link = a0;
       +        a->pt.x = a->pt.y = 0.0;
       +        a->amt = cur_sel.fp->thick;
       +        a->clr = cur_sel.fp->clr;
       +        a->fp = cur_sel.fp;
       +        a->typ = typ;
       +        return a;
       +}
       +
       +
       +/* This would be trivial except it's nice to preserve the selection in order to make
       +   it easy to undo a series of moves.  (There's no do_unrotate() because it's harder
       +   and less important to preserve the selection in that case.)
       +*/
       +void do_unmove(e_action* a)
       +{
       +        double tsav = cur_sel.t;
       +        unselect(0);
       +        move_fp(a->fp, -a->pt.x, -a->pt.y);
       +        if (a->fp == cur_sel.fp) {
       +                cur_sel.p.x -= a->pt.x;
       +                cur_sel.p.y -= a->pt.y;
       +        }
       +        cur_sel.t = tsav;
       +        reselect(0);
       +}
       +
       +
       +e_action* do_undo(e_action* a0)                /* pop off an e_action and (un)do it */
       +{
       +        e_action* a = a0;
       +        if (a==0)
       +                return 0;
       +        switch(a->typ) {
       +        case Ethick: a->fp->thick = a->amt;
       +                eresized(0);
       +                break;
       +        case Erecolor: a->fp->clr = a->clr;
       +                eresized(0);
       +                break;
       +        case Edelete: 
       +                a->fp->link = univ.p;
       +                univ.p = a->fp;
       +                grow_bb(&univ.bb, &a->fp->bb);
       +                eresized(0);
       +                break;
       +        case Emove:
       +                do_unmove(a);
       +                eresized(0);
       +                break;
       +        case Erotate:
       +                unselect(0);
       +                rotate_fp(a->fp, a->pt, -a->amt);
       +                eresized(0);
       +                break;
       +        }
       +        a0 = a->link;
       +        free(a);
       +        return a0;
       +}
       +
       +
       +
       +/********************************* Main menu  *********************************/
       +
       +enum m_index {     Mzoom_in,  Mzoom_out,  Munzoom,  Mslant,    Munslant,
       +                Msquare_up,  Mrecenter,  Mrecolor,  Mrestack,  Mread,
       +                Mwrite,      Mexit};
       +char* m_items[] = {"zoom in", "zoom out", "unzoom", "slant",   "unslant",
       +                "square up", "recenter", "recolor", "restack", "read",
       +                "write",     "exit", 0};
       +
       +Menu m_menu = {m_items, 0, 0};
       +
       +
       +void do_mmenu(int but, Mouse* m)
       +{
       +        int e, h = emenuhit(but, m, &m_menu);
       +        switch (h) {
       +        case Mzoom_in:
       +                disp_zoomin(egetrect(but,m));
       +                eresized(0);
       +                break;
       +        case Mzoom_out:
       +                disp_zoomout(egetrect(but,m));
       +                eresized(0);
       +                break;
       +        case Msquare_up:
       +                disp_squareup();
       +                eresized(0);
       +                break;
       +        case Munzoom:
       +                init_disp();
       +                eresized(0);
       +                break;
       +        case Mrecenter:
       +                if (get_1click(but, m, &bullseye)) {
       +                        recenter_disp(m->xy);
       +                        eresized(0);
       +                        lift_button(but, m, Never);
       +                }
       +                break;
       +        case Mslant:
       +                if (cur_sel.t>=0 && prev_sel.t>=0) {
       +                        slant_disp(prev_sel.p, cur_sel.p);
       +                        eresized(0);
       +                }
       +                break;
       +        case Munslant:
       +                univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
       +                eresized(0);
       +                break;
       +        case Mrecolor:
       +                do_recolor(but, m, 1);
       +                break;
       +        case Mrestack:
       +                fps_invert(&univ);
       +                eresized(0);
       +                break;
       +        case Mread:
       +                e = doinput(prompt_text("File:"));
       +                if (e==0)
       +                        eresized(0);
       +                else if (e<0)
       +                        show_mytext(" - can't read");
       +                else {
       +                        char ebuf[80];
       +                        snprintf(ebuf, 80, " - error line %d", e);
       +                        show_mytext(ebuf);
       +                }
       +                break;
       +        case Mwrite:
       +                if (!dooutput(prompt_text("File:")))
       +                        show_mytext(" - can't write");
       +                break;
       +        case Mexit:
       +                exits("");
       +        }
       +}
       +
       +
       +
       +/****************************** Handling events  ******************************/
       +
       +void doevent(void)
       +{
       +        ulong etype;
       +        int mobile;
       +        ulong mvtime;
       +        Event        ev;
       +
       +        etype = eread(Emouse|Ekeyboard, &ev);
       +        if(etype & Emouse) {
       +                if (ev.mouse.buttons & But1) {
       +                        do_select(ev.mouse.xy);
       +                        mvtime = Never;
       +                        mobile = !cantmv && cur_sel.t>=0;
       +                        if (mobile) {
       +                                mvtime = ev.mouse.msec + Mv_delay;
       +                                prepare_mv(cur_sel.fp);
       +                        }
       +                        if (!lift_button(1, &ev.mouse, mvtime) && mobile)
       +                                save_mv(do_move(1, &ev.mouse));
       +                } else if (ev.mouse.buttons & But2)
       +                        do_emenu(2, &ev.mouse);
       +                else if (ev.mouse.buttons & But3)
       +                        do_mmenu(3, &ev.mouse);
       +        }
       +        /* no need to check (etype & Ekeyboard)--there are no keyboard commands */
       +}
       +
       +
       +
       +/******************************** Main program ********************************/
       +
       +extern char* argv0;
       +
       +void usage(void)
       +{
       +        int i;
       +        fprintf(stderr,"Usage %s [options] [infile]\n", argv0);
       +        fprintf(stderr,
       +"option ::= -l logfile | -m\n"
       +"\n"
       +"Read a polygonal line graph in an ASCII format (one x y pair per line, delimited\n"
       +"by spaces with a label after each polyline), and view it interactively.  Use\n"
       +"standard input if no infile is specified.\n"
       +"Option -l specifies a file in which to log the coordinates of each point selected.\n"
       +"(Clicking a point with button one selects it and displays its coordinates and\n"
       +"the label of its polylone.)  Option -m allows polylines to be moved and rotated.\n"
       +"The polyline labels can use the following color names:"
       +        );
       +        for (i=0; clrtab[i].c!=DNofill; i++)
       +                fprintf(stderr,"%s%8s", (i%8==0 ? "\n" : "  "), clrtab[i].nam);
       +        fputc('\n', stderr);
       +        exits("usage");
       +}
       +
       +void main(int argc, char *argv[])
       +{
       +        int e;
       +
       +        ARGBEGIN {
       +        case 'm': cantmv=0;
       +                break;
       +        case 'l': logfil = fopen(ARGF(),"w");
       +                break;
       +        default: usage();
       +        } ARGEND
       +
       +        if(initdraw(0, 0, "gview") < 0)
       +                exits("initdraw");
       +        einit(Emouse|Ekeyboard);
       +
       +        e = doinput(*argv ? *argv : "-");
       +        if (e < 0) {
       +                fprintf(stderr,"Cannot read input file %s\n", *argv);
       +                exits("no valid input file");
       +        } else if (e > 0) {
       +                fprintf(stderr,"Bad syntax at line %d in input file\n", e);
       +                exits("bad syntax in input");
       +        }
       +        init_disp();
       +        init_clrtab();
       +        set_default_clrs(&univ, 0);
       +        adjust_border(display->defaultfont);
       +        cur_sel.t = prev_sel.t = -1;
       +        eresized(0);
       +        for(;;)
       +                doevent();
       +}