\input coursepages
\vsize=9.5truein
\voffset=-0.25truein
\input coursemacro.defs
\input verbatim

\def\runningtitle{PlotPath}
@package rpn.graphics;

import java.awt.*;
import rpn.doubles.Formats;

/** PlotPaths are the basic class drawn into RealCanvases.
    Drawing follows the PostScript model.
    Each path is an array of segments.
    Each segment is an array of coordinates.
    Stroking a path means drawing lines along the pieces.
    Filling it means turning each piece into a polygon. */
    
class NoCurrentPointException extends Exception {
}
   
public class PlotPath {
    final static int INC = 128;

    RealGraphics gfx;

    Polygon segment[];
    Polygon currentSegment = null;
    int maxSegmentNo = INC;
    int segmentNo;
    
    // mark last moveto:
    int last[];
    int cp[];
    int N = 0;

    public PlotPath(RealGraphics gfx) {
        this.gfx = gfx;
        segment = new Polygon[INC];
        segmentNo = 0;
    }

    int[] currentPoint() {
        return(cp);
    }

    // - moves --------------------------------------------

    void movetoPoint(int q[]) {
        if (segmentNo >= maxSegmentNo) {
            // System.out.println("overflow!");
            maxSegmentNo += INC;
            Polygon newseg[] = new Polygon[maxSegmentNo];
            for (int i=0;i<segmentNo;i++) {
                newseg[i] = segment[i];
            }
            segment = newseg;
        }
        segment[segmentNo] = new Polygon();
        currentSegment = segment[segmentNo++];
        currentSegment.addPoint(q[0], q[1]);
        last = q;
        cp = q;
    }

    public void moveto(double x, double y) {
        int q[] = gfx.toPoint(x, y);
        movetoPoint(q);
    }

    public void moveto(double p[]) {
        int q[] = gfx.toPoint(p);
        movetoPoint(q);
    }
    
    public void rmoveto(double x[]) throws NoCurrentPointException {
        if (cp != null) {
            int P[] = gfx.toRelativePoint(x);
            P[0] += cp[0];
            P[1] += cp[1];
            movetoPoint(P);
        } else {
            throw new NoCurrentPointException(); 
        }
    }

    public void rmoveto(double x, double y) throws NoCurrentPointException {
        if (cp != null) {
            int P[] = gfx.toRelativePoint(x, y);
            P[0] += cp[0];
            P[1] += cp[1];
            movetoPoint(P);
        } else {
            throw new NoCurrentPointException(); 
        }
    }

    // - lines --------------------------------------------

    public void lineto(double p[]) throws NoCurrentPointException {
        if (cp != null) {
            N++;
            int q[] = gfx.toPoint(p);
            currentSegment.addPoint(q[0], q[1]);
            cp = q;
        } else {
            throw new NoCurrentPointException(); 
        }
    }
    
    public void lineto(double x, double y) throws NoCurrentPointException {
        if (cp != null) {
            N++;
            int q[] = gfx.toPoint(x, y);
            currentSegment.addPoint(q[0], q[1]);
            cp = q;
        } else {
            throw new NoCurrentPointException(); 
        }
    }

    public void rlineto(double x, double y) throws NoCurrentPointException {
        if (cp != null) {
            N++;
            int P[] = gfx.toRelativePoint(x, y);
            cp[0] += P[0];
            cp[1] += P[1];
            currentSegment.addPoint(cp[0], cp[1]);
        } else {
            throw new NoCurrentPointException(); 
        }
    }

    // - curves -------------------------------------------
    
    public void curveto(double P[]) throws NoCurrentPointException {
        if (cp != null) {
            double P1[] = gfx.toFinePoint(P[0], P[1]);
            double P2[] = gfx.toFinePoint(P[2], P[3]);
            double P3[] = gfx.toFinePoint(P[4], P[5]);
            Curve.addCurve(currentSegment, P1, P2, P3);
            cp = new int[2];
            cp[0] = (int) P3[0];
            cp[1] = (int) P3[1];
        } else {
            throw new NoCurrentPointException(); 
        }
    }

    public void curveto(double P[][]) throws NoCurrentPointException {
        if (cp != null) {
            System.out.println("---------------------");
            System.out.println("P0 = " + RealPoint.toString(P[0]));
            System.out.println("P1 = " + RealPoint.toString(P[1]));
            System.out.println("P2 = " + RealPoint.toString(P[2]));
            P[0] = gfx.toFinePoint(P[0]);
            P[1] = gfx.toFinePoint(P[1]);
            P[2] = gfx.toFinePoint(P[2]);
            Curve.addCurve(currentSegment, P[0], P[1], P[2]);
            cp = new int[2];
            cp[0] = (int) P[2][0];
            cp[1] = (int) P[2][1];
        } else {
            throw new NoCurrentPointException(); 
        }
    }

    // ----------------------------------------------------
    
    void drawPath(Polygon P) {
        int n = P.npoints;
        Graphics g = gfx.getGraphics();
        if (n > 0) {
            int x = P.xpoints[0];
            int y = P.ypoints[0];
            for (int i=1;i<P.npoints;i++) {
                int x1 = P.xpoints[i];
                int y1 = P.ypoints[i];
                g.drawLine(x, y, x1, y1);
                x = x1; y = y1;
            }
        }
    }

    public void close() {
        if (last != null) {
            currentSegment.addPoint(last[0], last[1]);
            movetoPoint(last);
        }
    }

    public void stroke() {
        for (int i=0;i<segmentNo;i++) {
            drawPath(segment[i]);
        }
    }

    public void stroke(Color c) {
        Color tmp = gfx.getColor();
        gfx.setColor(c);
        for (int i=0;i<segmentNo;i++) {
            drawPath(segment[i]);
        }
        gfx.setColor(tmp);
    }

    public void fill() {
        for (int i=0;i<segmentNo;i++) {
            gfx.fillPolygon(segment[i]);
        }
    }

    public void fill(Color c) {
        Color tmp = gfx.getColor();
        gfx.setColor(c);
        for (int i=0;i<segmentNo;i++) {
            gfx.fillPolygon(segment[i]);
        }
        gfx.setColor(tmp);
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        for (int i=0;i<segmentNo;i++) {
            Polygon p = segment[i];
            for (int j=0;j<p.npoints;j++) {
                sb.append('(');
                sb.append(Formats.toString(p.xpoints[j]));
                sb.append(',');
                sb.append(Formats.toString(p.ypoints[j]));
                sb.append(')');
            }
        }
        return(new String(sb));
    } 
}

@\vfill\eject

\def\runningtitle{DrawCanvas}
@package rpn.graphics;

import java.awt.*;
import java.util.Stack;
import rpn.doubles.Formats;

interface PathSegment {
    void addTo(PlotPath p);
}

class MovetoSegment implements PathSegment {
    double x[];
    public MovetoSegment(double x[]) {
        this.x = x;
    }
    public void addTo(PlotPath p) {
        p.moveto(x);
    }
}

class LinetoSegment implements PathSegment {
    double x[];
    public LinetoSegment(double x[]) {
        this.x = x;
    }
    public void addTo(PlotPath p) {
        try {
            p.lineto(x);
        } catch(Exception e) { ; }
    }
}

class CurvetoSegment implements PathSegment {
    double x[][];
    public CurvetoSegment(double x[][]) {
        this.x = x;
    }
    
    public void addTo(PlotPath p) {
        try {
            p.curveto(x);
        } catch(Exception e) { ; }
    }
}

class CloseSegment implements PathSegment {
    public CloseSegment() {
    }

    public void addTo(PlotPath p) {
        p.close();
    }
}

// -- PathConstructor -----------------------------------------

class PathConstructor {
    final static int Quiet = 0, 
        Move = 1, Line = 2, Curve = 3;
    int state;
    double last[];
    double currentPoint[];
    boolean rendered = false;
    
    Stack segmentStack;
    
    public PathConstructor(RealGraphics g) {
        segmentStack = new Stack();
        state = Quiet;
    }
    
    public void setState(int s) {
        state = s;
    }
    
    int n = 0;
    final static int M = 3;
    public void addSegment(double p[]) {
        int n = segmentStack.size();
        switch(state) {
            case Move: {
                last = p;
                currentPoint = p;
                moveto(p);
                break;
            }
            case Line: {
                lineto(p);
                currentPoint = p;
                break;
            }
            case Curve: {
                double q[][] = new double[3][];
                q[0] = new double[2];
                q[0][0] = (2*currentPoint[0] + p[0])/3;
                q[0][1] = (2*currentPoint[1] + p[1])/3;
                q[1] = new double[2];
                q[1][0] = (currentPoint[0] + 2*p[0])/3;
                q[1][1] = (currentPoint[1] + 2*p[1])/3;
                q[2] = p;
                curveto(q);
                System.out.println("current = " + RealPoint.toString(currentPoint));
                System.out.println("q[0] = " + RealPoint.toString(q[0]));
                System.out.println("q[1] = " + RealPoint.toString(q[1]));
                System.out.println("q[2] = " + RealPoint.toString(q[2]));
                currentPoint = p;
                break;
            }
        }
    }
    
    // -------------------------------------------
    
    void moveto(double x[]) {
        PathSegment ds = new MovetoSegment(x);
        segmentStack.push(ds);
    }
    void lineto(double x[]) {
        PathSegment ds = new LinetoSegment(x);
        segmentStack.push(ds);
    }
    void curveto(double x[][]) {
        PathSegment ds = new CurvetoSegment(x);
        segmentStack.push(ds);
    }
    void close() {
        PathSegment ds = new CloseSegment();
        segmentStack.push(ds);
    }
    
    // ---------------------------------------------
    
    public void draw(RealGraphics gfx) {
        PlotPath p = new PlotPath(gfx);
        for (int i=0;i<segmentStack.size();i++) {
            PathSegment s = (PathSegment) segmentStack.elementAt(i);
            s.addTo(p);
        }
        p.stroke(Color.lightGray);
    }
}

// -- DrawingPath ----------------------------------------------

class DrawingPath {
    final static int None = 0, Stroke = 1,
        Fill = 2;
    PathSegment segment[];
    Color c;
    int type;
    
    DrawingPath(PathConstructor p, 
            int type, Color c) {
        this.type = type;
        this.c = c;
        int n = p.segmentStack.size();
        segment = new PathSegment[n];
        for (int i=0;i<n;i++) {
            segment[i] = (PathSegment) p.segmentStack.elementAt(i);
        }
        p.rendered = true;
    }
    // --------------------------------------------------
    
    public void draw(RealGraphics gfx) {
        PlotPath p = new PlotPath(gfx);
        System.out.println("path size = " + segment.length);
        for (int i=0;i<segment.length;i++) {
            segment[i].addTo(p);
        }
        switch(type) {
            case Stroke: {
                p.stroke(c);
                break;
            }
            case Fill: {
                p.fill(c);
                break;
            }
        }
    }
    
}

// -- DrawCanvas ----------------------------------------------

public class DrawCanvas extends Canvas {
    int W, H, w, h;
    Stack pathStack;
    RealGraphics gfx;
    PathConstructor cp = null;
    Color colour = Color.black;
    
    public DrawCanvas(int w, int h) {
        this.w = w; this.h = h;
        W = w; H = h;
        resize(w, h);
        setBackground(Color.white);
        pathStack = new Stack();
    }

    /** Reverses the normal ordering of paint() and 
        update() for flicker-free performance. */
    public void paint(Graphics g) {
        update(g);
    }

    Image img;
    /** Usually overridden. */
    public void update(Graphics g) {
        if (gfx == null) {
            img = createImage(W, H);
            Graphics G = img.getGraphics();
            gfx = new RealGraphics(G, w, h);
        }
        System.out.println("path count = " + pathStack.size());
        for (int i=0;i<pathStack.size();i++) {
            DrawingPath path = (DrawingPath) pathStack.elementAt(i);
            path.draw(gfx);
        }
        if (cp != null && !cp.rendered) {
            cp.draw(gfx);
        }
        g.drawImage(img, 0, 0, null);
        System.out.println("================================");
    }
    
    // - button events from the frame -------------------------
    
    public void setColor(Color c) {
        colour = c;
    }
    
    public void clear() {
        pathStack = new Stack();
        cp = null;
        gfx.clear();
        repaint();
    }
    
    public void newPath() {
        cp = new PathConstructor(gfx);
        cp.setState(PathConstructor.Move);
    }
    
    public void line() {
        if (cp != null) {
            cp.setState(PathConstructor.Line);
        }
    }
    
    public void curve() {
        if (cp != null) {
            cp.setState(PathConstructor.Curve);
        }
    }
    
    public void close() {
        if (cp != null) {
            cp.close();
        }
    }
        
    public void stroke() {
        if (cp != null) {
            DrawingPath dp = new DrawingPath(cp, DrawingPath.Stroke, colour);
            pathStack.push(dp);
        }
        repaint();
    }
    
    public void fill() {
        if (cp != null) {
            DrawingPath dp = new DrawingPath(cp, DrawingPath.Fill, colour);
            pathStack.push(dp);
        }
        repaint();
    }
    
    // - mouse events ------------------------------------
    
    public boolean mouseDown(Event e, int x, int y) {
        double P[] = gfx.toRealPoint(x, y);
        System.out.println("mouse at " + RealPoint.toString(P));
        cp.addSegment(P);
        repaint();
        return(true);
    }
    
    public boolean mouseDrag(Event e, int x, int y) {
        return(true);
    }
    
    public boolean mouseUp(Event e, int x, int y) {
        return(true);
    }
}
    
@
\bye

\vfill\eject

\def\runningtitle{DrawFrame}
@package rpn.graphics;

import java.awt.*;

class ColourPanel extends Panel {
    Button button[];
    int N = 5;
    Color c[] = { Color.black,
        Color.lightGray,
        Color.red,
        Color.green,
        Color.blue};
    DrawCanvas canvas;

    public ColourPanel(DrawCanvas canvas) {
        this.canvas = canvas;
        setLayout(new GridLayout(1,N));
        button = new Button[N];
        for (int i=0;i<N;i++) {
            button[i] = new Button();
            button[i].setBackground(c[i]);
            add(button[i]);
        }
    }
    
    public boolean handleEvent(Event evt) {
        if (evt.id == Event.ACTION_EVENT) {
            for (int i=0;i<N;i++) {
                if (evt.target == button[i]) {
                    canvas.setColor(c[i]);
                }
            }
        }
        return(true);
    }
}

class DrawFrame extends Frame {
    Panel bp;
    public DrawCanvas canvas;
    Panel cp;
    final static int Newpath=0,  
            Line=1, 
            Curve=2, 
            Stroke=3, 
            Fill=4, 
            Close=5,
            Clear=6; 
    String buttonString[] = {"newpath", 
                "line",
                "curve", 
                "stroke", 
                "fill", 
                "close",
                "clear", 
                };
    int buttonNo = buttonString.length;     
    Button button[] = new Button[buttonNo];
    public DrawFrame(String t, int w, int h) {
        super(t);
        Font f = new Font("Helvetica", Font.BOLD, 14);
        setFont(f);
        resize(w, h);
        setLayout(new BorderLayout(0, 0));
        canvas = new DrawCanvas(w, h);
        add("Center", canvas);
        bp = new Panel();
        
        bp.setLayout(new GridLayout(buttonNo+4, 1));
        bp.add(new Panel());
        cp = new ColourPanel(canvas);        
        bp.add(cp);
        for (int i=0;i<buttonNo;i++) {
            button[i] = new Button(buttonString[i]);
            bp.add(button[i]);
        }
        bp.add(new Panel());
        bp.add(new Panel());
        add("East", bp);
        pack();
        show();
    }
    
    int active = Newpath;
    
    public boolean handleEvent(Event evt) {
        if (evt.id == Event.ACTION_EVENT) {
            button[active].setBackground(Color.lightGray);
            if (evt.target == button[Newpath]) { 
                active = Newpath;
                canvas.newPath();
            } else 
            if (evt.target == button[Line]) {
                active = Line;
                canvas.line();
            } else 
            if (evt.target == button[Curve]) {
                active = Curve;
                canvas.curve();
            } else 
            if (evt.target == button[Stroke]) {
                active = Stroke;
                canvas.stroke();
            } else 
            if (evt.target == button[Fill]) {
                active = Fill;
                canvas.fill();
            } else 
            if (evt.target == button[Close]) {
                active = Close;
                canvas.close();
            } else        
            if (evt.target == button[Clear]) {
                active = Clear;
                canvas.clear();
            } else 
            button[active].setBackground(Color.pink);
        } else if (evt.id == Event.WINDOW_DESTROY) {
            System.exit(0);
        } else if (evt.id != Event.LOST_FOCUS 
                && evt.id != Event.GOT_FOCUS
                && evt.id != Event.MOUSE_MOVE
                && evt.id != Event.MOUSE_ENTER
                && evt.id != Event.MOUSE_EXIT ) {
            // System.out.println(rpn.EventDisplay.toString(evt));
        }
        return(true);
    }
}
@
\bye
\vfill\eject

\def\runningtitle{RealGraphics}
@package rpn.graphics;

import java.awt.*;
import rpn.doubles.Formats;

/** 

The class RealGraphics is the basic class in all 
these floating point graphics routines.  
(*) It has a real coordinate system, 
initiated constructed with setCorners().
(*) Graphics drawing commands imitate PostScript.
(*) The basic class of things drawn is `PlotPath'.  
After one is instantiated,
we build it with methods `moveto', `lineto', etc.
and draw it with `stroke', `fill'.  Coordinate systems are
changed with commands `scale', `rotate', `translate', `zoom'.
There is no true graphics state, however; colours are usually
used in the call directly. 

*/

public class RealGraphics {
    double m[][];
    double t[];
    final static double INFTY = 32783.0;
    Color currentColour = Color.black;
    Color background = Color.white;
    Graphics gfx;
    int w, h;

    /** The variables w and h determine the canvas size in pixels. */

    public RealGraphics(Graphics gfx, int w, int h) {
        this.gfx = gfx;
        this.w = w;
        this.h = h;
        setBackground(Color.white);
        m = new double[2][2];
        t = new double[2];
        m[0][0] = 1.0; m[0][1] =  0.0;
        m[1][0] = 0.0; m[1][1] = -1.0; 
        t[0] = 0.0;
        t[1] = (double) h;
    }
    
    public void setBackground(Color c) {
        background = c;
    }
    
    public Color getBackground(Color c) {
        return(background);
    }
    
    public void setColor(Color c) {
        gfx.setColor(c);
        currentColour = c;
    }
    
    public Color getColor() {
        return(currentColour);
    }
    
    public void setBounds(int w, int h) {
        this.w = w; this.h = h;
    }
    
    public Graphics getGraphics() {
        return(gfx);
    }
    
    public void drawPolygon(Polygon p) {
        gfx.drawPolygon(p);
    }
    
    public void fillPolygon(Polygon p) {
        gfx.fillPolygon(p);
    }
    
    /** The point
    (llx, lly) becomes lower left, (urx, ury) upper right. */
    public void setCorners(double llx, double lly, 
            double urx, double ury) {
        
        m[0][0] = (double) w/(urx - llx);
        m[0][1] = 0.0;
        m[1][1] = - (double) h/(ury - lly);
        m[1][0] = 0.0;
        t[0] = - m[0][0]*llx;
        t[1] = - m[1][1]*ury;
    }

    /** This returns the floating point corners of the pixel at upper left. 
    This can be used to determine true pixel size, often important 
    for knowing how fine-grained to do plots at. */

    public double[][] pixel() {
        double b[][] = new double[4][];
        b[0] = toRealPoint(0, 0);
        b[1] = toRealPoint(1, 0);
        b[2] = toRealPoint(1, 1);
        b[3] = toRealPoint(0, 1);
        return(b);
    }
    
    /** Assumes rectangular coordinates. */
    public double[] pixelDimensions() {
        double P[] = toRealPoint(0, 0);
        double Q[] = toRealPoint(1, 1);
        Q[0] = Math.abs(Q[0] - P[0]); Q[1] = Math.abs(Q[1] - P[0]);
        return(Q);
    }

    /** Returns a pixel of width n, centered at P */
    public double[][] pixel(double P[], int n) {
        int p[] = toPoint(P);
        p[0] -= (n/2); p[1] -= (n/2);
        double b[][] = new double[4][];
        b[0] = toRealPoint(p);
        p[0] += n;
        b[1] = toRealPoint(p);
        p[1] += n;
        b[2] = toRealPoint(p);
        p[0] -= n;
        b[3] = toRealPoint(p);
        return(b);
    }

    /** Locates corners: ll, lr, ur, ul. */
    public double[][] getRealBounds() {
        double b[][] = new double[4][];
        b[0] = toRealPoint(0, h);
        b[1] = toRealPoint(w, h);
        b[2] = toRealPoint(w, 0);
        b[3] = toRealPoint(0, 0);
        return(b);
    }
    
    public int[] getBounds() {
        int b[] = new int[2];
        b[0] = w; b[1] = h;
        return(b);
    }
    
    /** Paints a blank canvas onto the offscreen image. */
    public void clear() {
        gfx.setColor(background);
        gfx.fillRect(0, 0, w, h);
        gfx.setColor(currentColour);
    }
    
    /** Returns pixel with coordinates P. 
    Coordinates can be be infinity but not NaN. */
    public int[] toPoint(double p[]) {
        return(toPoint(p[0], p[1]));
    }
    
    /** Returns pixel with coordinates (x, y). 
    Coordinates can be be infinity but not NaN. */
    public int[] toPoint(double x, double y) {
        double x0 = m[0][0]*x + m[0][1]*y + t[0];
        double y0 = m[1][0]*x + m[1][1]*y + t[1];
        // System.out.println("x, y = " + x + ", " + y);
        // System.out.println("x0, y0 = " + x0 + ", " + y0);
        int q[] = new int[2];
        // never let it wrap around
        if (-INFTY < x0 && x0 < INFTY) {
            q[0] = (int) Math.round(x0);
        } else if (x0 <= -INFTY || x0 == Double.NEGATIVE_INFINITY) {
            q[0] = (int) -INFTY;
        } else {
            q[0] = (int) INFTY;
        }
        if (-INFTY < y0 && y0 < INFTY) {
            q[1] = (int) Math.round(y0);
        } else if (y0 <= -INFTY || y0 == Double.NEGATIVE_INFINITY) {
            q[1] = (int) -INFTY;
        } else {
            q[1] = (int) INFTY;
        }
        // System.out.println("x1, y1 = " + q[0] + ", " + q[1]);
        return(q);
    }

    public int[] toRelativePoint(double x, double y) {
        double x0 = m[0][0]*x + m[0][1]*y;
        double y0 = m[1][0]*x + m[1][1]*y;
        int q[] = new int[2];
        // never let it wrap around
        if (-INFTY < x0 && x0 < INFTY) {
            q[0] = (int) Math.round(x0);
        } else if (x0 <= -INFTY || x0 == Double.NEGATIVE_INFINITY) {
            q[0] = (int) -INFTY;
        } else {
            q[0] = (int) INFTY;
        }
        if (-INFTY < y0 && y0 < INFTY) {
            q[1] = (int) Math.round(y0);
        } else if (y0 <= -INFTY || y0 == Double.NEGATIVE_INFINITY) {
            q[1] = (int) -INFTY;
        } else {
            q[1] = (int) INFTY;
        }
        return(q);
    }

    public int[] toRelativePoint(double x[]) {
        return(toRelativePoint(x[0], x[1]));
    }

    /** Pixels to floating point coordinates. */
    public double[] toRealPoint(int p[]) {
        return(toRealPoint(p[0], p[1]));
    }

    /** Pixels to floating point coordinates. */
    // pixels to coords
    // p = m*q + t, q = m^{-1}(p - t)
    public double[] toRealPoint(int x, int y) {
        double x0 = (double) x - t[0];
        double y0 = (double) y - t[1];
        double d = m[0][0]*m[1][1] - m[0][1]*m[1][0];
        double x1 =  m[1][1]*x0 - m[0][1]*y0;
        double y1 = -m[1][0]*x0 + m[0][0]*y0;
        x1 /= d;
        y1 /= d;
        double q[] = new double[2];
        q[0] = x1; q[1] = y1;
        return(q);
    }

    /** Real coordinates to pixels, but in real numbers. */
    double[] toFinePoint(double p[]) {
        double x0 = m[0][0]*p[0] + m[0][1]*p[1] + t[0];
        double y0 = m[1][0]*p[0] + m[1][1]*p[1] + t[1];
        double q[] = new double[2];
        q[0] = x0; q[1] = y0;
        return(q);
    }
    
    /** Real coordinates to pixels, but in real numbers. */
    double[] toFinePoint(double x, double y) {
        double x0 = m[0][0]*x + m[0][1]*y + t[0];
        double y0 = m[1][0]*x + m[1][1]*y + t[1];
        double q[] = new double[2];
        q[0] = x0; q[1] = y0;
        return(q);
    }

    /** Returns true if visible on the screen.
    Returns false if x or y is NaN. */ 
    // since all comparisons with NaN are false
    public boolean isVisible(double x, double y) {
        int p[] = toPoint(x, y);
        if (0 <= p[0] && p[0] < w 
            && 0 <= p[1] && p[1] < h) {
            return(true);
        }
        else {
            return(false);
        }
    }

    /** Returns true if visible on the screen.
    Returns false if x or y is NaN. */ 
    public boolean isVisible(double p[]) {
        return(isVisible(p[0], p[1]));
    }

    /** Returns true if visible on the screen. */ 
    public boolean isVisible(int p[]) {
        // doesn't use xmin etc.
        if (0 <= p[0] && p[0] < w 
            && 0 <= p[1] && p[1] < h) {
            return(true);
        }
        else {
            return(false);
        }
    }

    /** Follows PostScript model, but using column vectors.
        All of these postmultiply 
        the CTM by the relevant matrix. */ 

    public void scale(double a, double b) {
        m[0][0] *= a;
        m[1][0] *= a;
        m[0][1] *= b;
        m[1][1] *= b;
    }

    /** Follows PostScript model. */
    public void translate(double a, double b) {
        t[0] += m[0][0]*a + m[0][1]*b;
        t[1] += m[1][0]*a + m[1][1]*b;
    }
    
    /** Follows PostScript model. */
    // multiply on the right by rotation matrix
    public void rotate(double theta) {
        double c = Math.cos(theta);
        double s = Math.sin(theta);
        
        double x00 =  m[0][0]*c + m[0][1]*s;
        double x01 = -m[0][0]*s + m[0][1]*c;
        double x10 =  m[1][0]*c + m[1][1]*s;
        double x11 = -m[1][0]*s + m[1][1]*c;
        m[0][0] = x00;
        m[0][1] = x01;
        m[1][0] = x10;
        m[1][1] = x11;
    }

    // z = magnification, t = new origin
    public void zoom(double z, double t[]) {
        Point s;
        double r[];

        // calculate new matrix
        scale(z, z);
        r = toRealPoint(w/2, h/2);
        translate(r[0] - t[0], r[1] - t[1]);
    }

    public void zoom(double mx, double my, double t[]) {
        Point s;
        double r[];

        // calculate new matrix
        scale(mx, my);
        r = toRealPoint(w/2, h/2);
        translate(r[0] - t[0], r[1] - t[1]);
    }
    
    public void setFont(Font f) {
        gfx.setFont(f);
    }
    
    
    public void drawString(String s, double Q[], Color c) {
        Color tmp = gfx.getColor();
        gfx.setColor(c);
        int P[] = toPoint(Q);
        gfx.drawString(s, P[0], P[1]);
        gfx.setColor(tmp);
    }
    
    /** Uses float coordinates. */
    public void drawString(String s, double x, double y, Color c) {
        Color tmp = gfx.getColor();
        gfx.setColor(c);
        int P[] = toPoint(x, y);
        gfx.drawString(s, P[0], P[1]);
        gfx.setColor(tmp);
    }
    
    /** Uses float coordinates. */
    public void drawString(String s, double Q[]) {
        int P[] = toPoint(Q);
        gfx.drawString(s, P[0], P[1]);
    }
    
    /** Uses float coordinates. */
    public void drawString(String s, double x, double y) {
        int P[] = toPoint(x, y);
        gfx.drawString(s, P[0], P[1]);
    }
    
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append(Formats.toString(m[0][0], 6, 8));
        sb.append(" ");
        sb.append(Formats.toString(m[0][1], 6, 8));
        sb.append(" \n");
        sb.append(Formats.toString(m[1][0], 6, 8));
        sb.append(" ");
        sb.append(Formats.toString(m[1][1], 6, 8));
        sb.append(" \n");
        sb.append(Formats.toString(t[0], 6, 8));
        sb.append(" ");
        sb.append(Formats.toString(t[1], 6, 8));
        sb.append(" \n");
        return(new String(sb));
    }
    

}
@\vfill\eject

\def\runningtitle{CalculatorCanvas}
@package rpn.graphics;

import java.awt.*;
import java.util.Stack;
import rpn.doubles.Formats;

class CalculatorPath {
    final static int None = -1, Stroke = 0, Fill = 1, Clip = 2;
    int mode;
    Color colour = Color.black;
    PlotPath p;
    public CalculatorPath(PlotPath p) {
        this.p = p;
        mode = None;
    }

    public CalculatorPath(PlotPath p, int m) {
        this.p = p;
        mode = m;
    }

    public CalculatorPath(PlotPath p, int m, Color c) {
        this.p = p;
        mode = m;
        colour = c;
    }

    public void setMode(int m) {
        mode = m;
    }

    public void setColor(Color c) {
        colour = c;
    }

    public void draw() {
        switch(mode) {
            case Stroke: {
                p.stroke(colour);
                break;
            }
            case Fill: {
                p.fill(colour);
                break;
            }
            case Clip: { }
            default: {
                break;
            }
        }
    }
}

public class CalculatorCanvas extends Canvas {
    int w, h;
    double W, H;
    RealGraphics gfx;
    Image img;
    Stack pathStack;
    Color currentColour = Color.black;

    public CalculatorCanvas(int w, int h) {
        this(w, h, 10, 10);
    }
    
    public CalculatorCanvas(int w, int h, double W, double H) {
        this.w = w;
        this.h = h;
        this.W = W;
        this.H = H;
        pathStack = new Stack();
        resize(w, h);
    }
    
    /** Reverses the normal ordering of paint() and 
        update() for flicker-free performance. */
    public void paint(Graphics g) {
        update(g);
    }

    /** Usually overridden. */
    public void update(Graphics g) {
        if (gfx == null) {
            img = createImage(w, h);
            Graphics G = img.getGraphics();
            gfx = new RealGraphics(G, w, h);
            gfx.setCorners(-W, -H, W, H);
        }
        gfx.clear();
        for (int i=0;i<pathStack.size();i++) {
            CalculatorPath p = 
                (CalculatorPath) pathStack.elementAt(i);
            p.draw();
        }
        g.drawImage(img, 0, 0, null);
    }
    
    public void init() {
        gfx.setCorners(-W, -H, W, H);
        currentColour = Color.black;
        gfx.setColor(Color.black);
        clear();
    }
    
    PlotPath cp = null;
    
    public PlotPath currentPath() {
        return(cp);
    }
    
    public void newpath() {
        cp = new PlotPath(gfx);
    }
    
    public boolean moveto(double x, double y) {
        if (cp != null) {
            cp.moveto(x, y);
            return(true);
        } else return(false);
    }

    public boolean lineto(double x, double y) throws NoCurrentPointException {
        try {
            cp.lineto(x, y);
            return(true);
        } catch (Exception e) {
            throw new NoCurrentPointException();
        }
    }

    public boolean curveto(double p[]) 
            throws NoCurrentPointException {
        try {
            cp.curveto(p);
            return(true);
        } catch (Exception e) {
            throw new NoCurrentPointException();
        }
    }

    public boolean stroke() {
        if (cp != null) {
            CalculatorPath p 
                = new CalculatorPath(cp, 
                    CalculatorPath.Stroke,
                    currentColour);
            pathStack.push(p);
            return(true);
        } else return(false);
    }

    public boolean fill() {
        if (cp != null) {
            CalculatorPath p 
                = new CalculatorPath(cp, 
                    CalculatorPath.Fill,
                    currentColour);
            pathStack.push(p);
            return(true);
        } else return(false);
    }
    
    public void rotate(double x) {
        gfx.rotate(x);
    }

    public void scale(double x, double y) {
        gfx.scale(x, y);
    }

    public void translate(double x, double y) {
        gfx.translate(x, y);
    }
    
    public void clear() {
        gfx.clear();
        pathStack = new Stack();
    }

    public void setColor(double r, double g, double b) {
        currentColour = new Color((float) r, (float) g, (float) b);
    }
}
@\vfill\eject

\def\runningtitle{PlotPath}
@package rpn.graphics;

import java.awt.*;
import rpn.doubles.Formats;

/** PlotPaths are the basic class drawn into RealCanvases.
    Drawing follows the PostScript model.
    Each path is an array of segments.
    Each segment is an array of coordinates.
    Stroking a path means drawing lines along the pieces.
    Filling it means turning each piece into a polygon. */
    
class NoCurrentPointException extends Exception {
}
   
public class PlotPath {
    final static int INC = 128;

    RealGraphics gfx;

    Polygon segment[];
    Polygon currentSegment = null;
    int maxSegmentNo = INC;
    int segmentNo;
    
    // mark last moveto:
    int last[];
    int cp[];
    int N = 0;

    public PlotPath(RealGraphics gfx) {
        this.gfx = gfx;
        segment = new Polygon[INC];
        segmentNo = 0;
    }

    int[] currentPoint() {
        return(cp);
    }

    // - moves --------------------------------------------

    void movetoPoint(int q[]) {
        if (segmentNo >= maxSegmentNo) {
            // System.out.println("overflow!");
            maxSegmentNo += INC;
            Polygon newseg[] = new Polygon[maxSegmentNo];
            for (int i=0;i<segmentNo;i++) {
                newseg[i] = segment[i];
            }
            segment = newseg;
        }
        segment[segmentNo] = new Polygon();
        currentSegment = segment[segmentNo++];
        currentSegment.addPoint(q[0], q[1]);
        last = q;
        cp = q;
    }

    public void moveto(double x, double y) {
        int q[] = gfx.toPoint(x, y);
        movetoPoint(q);
    }

    public void moveto(double p[]) {
        int q[] = gfx.toPoint(p);
        movetoPoint(q);
    }
    
    public void rmoveto(double x[]) throws NoCurrentPointException {
        if (cp != null) {
            int P[] = gfx.toRelativePoint(x);
            P[0] += cp[0];
            P[1] += cp[1];
            movetoPoint(P);
        } else {
            throw new NoCurrentPointException(); 
        }
    }

    public void rmoveto(double x, double y) throws NoCurrentPointException {
        if (cp != null) {
            int P[] = gfx.toRelativePoint(x, y);
            P[0] += cp[0];
            P[1] += cp[1];
            movetoPoint(P);
        } else {
            throw new NoCurrentPointException(); 
        }
    }

    // - lines --------------------------------------------

    public void lineto(double p[]) throws NoCurrentPointException {
        if (cp != null) {
            N++;
            int q[] = gfx.toPoint(p);
            currentSegment.addPoint(q[0], q[1]);
            cp = q;
        } else {
            throw new NoCurrentPointException(); 
        }
    }
    
    public void lineto(double x, double y) throws NoCurrentPointException {
        if (cp != null) {
            N++;
            int q[] = gfx.toPoint(x, y);
            currentSegment.addPoint(q[0], q[1]);
            cp = q;
        } else {
            throw new NoCurrentPointException(); 
        }
    }

    public void rlineto(double x, double y) throws NoCurrentPointException {
        if (cp != null) {
            N++;
            int P[] = gfx.toRelativePoint(x, y);
            cp[0] += P[0];
            cp[1] += P[1];
            currentSegment.addPoint(cp[0], cp[1]);
        } else {
            throw new NoCurrentPointException(); 
        }
    }

    // - curves -------------------------------------------
    
    public void curveto(double P[]) throws NoCurrentPointException {
        if (cp != null) {
            double P1[] = gfx.toFinePoint(P[0], P[1]);
            double P2[] = gfx.toFinePoint(P[2], P[3]);
            double P3[] = gfx.toFinePoint(P[4], P[5]);
            Curve.addCurve(currentSegment, P1, P2, P3);
            cp = new int[2];
            cp[0] = (int) P3[0];
            cp[1] = (int) P3[1];
        } else {
            throw new NoCurrentPointException(); 
        }
    }

    public void curveto(double P[][]) throws NoCurrentPointException {
        if (cp != null) {
            System.out.println("---------------------");
            System.out.println("P0 = " + RealPoint.toString(P[0]));
            System.out.println("P1 = " + RealPoint.toString(P[1]));
            System.out.println("P2 = " + RealPoint.toString(P[2]));
            P[0] = gfx.toFinePoint(P[0]);
            P[1] = gfx.toFinePoint(P[1]);
            P[2] = gfx.toFinePoint(P[2]);
            Curve.addCurve(currentSegment, P[0], P[1], P[2]);
            cp = new int[2];
            cp[0] = (int) P[2][0];
            cp[1] = (int) P[2][1];
        } else {
            throw new NoCurrentPointException(); 
        }
    }

    // ----------------------------------------------------
    
    void drawPath(Polygon P) {
        int n = P.npoints;
        Graphics g = gfx.getGraphics();
        if (n > 0) {
            int x = P.xpoints[0];
            int y = P.ypoints[0];
            for (int i=1;i<P.npoints;i++) {
                int x1 = P.xpoints[i];
                int y1 = P.ypoints[i];
                g.drawLine(x, y, x1, y1);
                x = x1; y = y1;
            }
        }
    }

    public void close() {
        if (last != null) {
            currentSegment.addPoint(last[0], last[1]);
            movetoPoint(last);
        }
    }

    public void stroke() {
        for (int i=0;i<segmentNo;i++) {
            drawPath(segment[i]);
        }
    }

    public void stroke(Color c) {
        Color tmp = gfx.getColor();
        gfx.setColor(c);
        for (int i=0;i<segmentNo;i++) {
            drawPath(segment[i]);
        }
        gfx.setColor(tmp);
    }

    public void fill() {
        for (int i=0;i<segmentNo;i++) {
            gfx.fillPolygon(segment[i]);
        }
    }

    public void fill(Color c) {
        Color tmp = gfx.getColor();
        gfx.setColor(c);
        for (int i=0;i<segmentNo;i++) {
            gfx.fillPolygon(segment[i]);
        }
        gfx.setColor(tmp);
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        for (int i=0;i<segmentNo;i++) {
            Polygon p = segment[i];
            for (int j=0;j<p.npoints;j++) {
                sb.append('(');
                sb.append(Formats.toString(p.xpoints[j]));
                sb.append(',');
                sb.append(Formats.toString(p.ypoints[j]));
                sb.append(')');
            }
        }
        return(new String(sb));
    } 
}

@\vfill\eject

\bye

