/*
 *   PSDrawing.java : This file is part of pstoedit.
 *   Java applet needed to run the java code generated by pstoedit -f java2
 *
 *   This applet is called from HTML via one of the following ways:
 *   Netscape Navigator:
 *     <embed type="application/x-java-applet;version=1.2"
 *       pluginspage="http://java.sun.com/products/plugin/1.2/plugin-install.html"
 *       code="MYCLASS.class" width=700 height=500 align="baseline">
 *     </embed>
 *   Internet Exploder:
 *     <object classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
 *         width="700" height="500" align="baseline"
 *         codebase="http://java.sun.com/products/plugin/1.2.2/jinstall-1_2_2-win.cab#Version=1,2,2,0">
 *       <param name="code"       value="myclass.class">
 *       <param name="type"       value="application/x-java-applet;version=1.2.2">
 *     </object>
 *   replace MYCLASS by whatever you specified after the -f java2 option,
 *   e.g. -f java2:anotherclassname
 *   For mor information cf. the readme file.
 *
 *   Copyright (C) 1998-1999 Wolfgang Glunz, wglunz35_AT_pstoedit.net 
 *                 2000      Gisbert W. Selke, gisbert@tapirsoft.de
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import javax.swing.*;

public class PSDrawing extends JApplet {

  private static byte debug = 1;

  protected PSPages pspages = new PSPages();
  protected int pagenr = 1;
  protected JButton previousButton = null;
  protected JButton nextButton     = null;
  protected JButton zoominButton   = null;
  protected JButton zoomoutButton  = null;
  protected JButton rotateButton   = null;
  protected JPanel drawingPanel    = null;
  protected JPanel buttonPanel     = null;
  protected Cursor savedCursor     = null;
  protected int startMouseX = 0, startMouseY = 0;
  double currentXOffset  = 0.0, currentYOffset = 0.0;
  double currentZoom  = 1.0;
  double currentAngle = 0.0;
  protected double zoomFactor = 1.2;
  protected double deltaAngle = Math.toRadians(-90.0);


  public void init() {
    // derived classes must first fill pspages, then chain to this init()
    boolean doZoom   = false;
    boolean doRotate = false;
    Double[] tempValues;

    getContentPane().setBackground(Color.white);
    getContentPane().setLayout(new BorderLayout(0, 0));
    drawingPanel = new JPanel();

    tempValues = parseParameters("zoom", 2);
    if (tempValues != null) {
      if (tempValues[0] != null) currentZoom = Math.abs(tempValues[0].doubleValue());
      if ((tempValues[1] != null) &&
          (Math.abs(tempValues[1].doubleValue()) >= 1.01)) {
        zoomFactor = Math.abs(tempValues[1].doubleValue());
      }
      doZoom = (currentZoom != 0);
      if (!doZoom) currentZoom = 1.0;
    }

    tempValues = parseParameters("rotate", 2);
    if (tempValues != null) {
      if (tempValues[0] != null) currentAngle = Math.toRadians(-tempValues[0].doubleValue());
      if (tempValues[1] != null) deltaAngle = Math.toRadians(-tempValues[1].doubleValue());
      doRotate = (deltaAngle != 0);
    }

    tempValues = parseParameters("offset", 2);
    if (tempValues != null) {
      if (tempValues[0] != null) currentXOffset = tempValues[0].doubleValue();
      if (tempValues[1] != null) currentYOffset = tempValues[1].doubleValue();
    }

    tempValues = parseParameters("page", 1);
    if (tempValues != null) {
      if (tempValues[0] != null) pagenr = tempValues[0].intValue();
      if (pagenr < 1) pagenr = 1;
      if (pagenr > pspages.size()) pagenr = pspages.size();
    }

    drawingPanel.addMouseListener(
      new javax.swing.event.MouseInputAdapter() {
        public void mousePressed(MouseEvent e) {
          if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == 0) return;
          startMouseX = e.getX();
          startMouseY = e.getY();
          savedCursor = getCursor();
          setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
        }
        public void mouseReleased(MouseEvent e) {
          if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == 0) return;
          calculateOffsets(e.getX()-startMouseX, e.getY()-startMouseY);
          startMouseX = e.getX();
          startMouseY = e.getY();
          paint((Graphics2D)getGraphics());
          setCursor(savedCursor);
        }
      }
    );

    getContentPane().add(drawingPanel, BorderLayout.CENTER);

    buildButtonPanel(doZoom, doRotate);
    if (buttonPanel != null) getContentPane().add(buttonPanel, BorderLayout.SOUTH);

    if (debug > 1) System.err.println("setting up pages done");
  }


  void buildButtonPanel(boolean doZoom, boolean doRotate) {

    boolean haveDefaultButton = false;
    /* GIF bitmaps for buttons as byte arrays so that no external resources are needed: */
    byte[][] buttonIcons = {
      /* previous: */
      { 71,73,70,56,57,97,24,0,24,0,-128,0,0,0,0,0,-50,-49,-50,33,
        -7,4,1,0,0,1,0,44,0,0,0,0,24,0,24,0,64,2,52,-116,
        -113,-87,27,0,-116,30,108,79,-50,-23,110,-54,-70,-11,15,-122,-108,7,114,-102,
        -91,-104,16,-70,-78,-30,11,-57,-97,-29,118,28,-115,-45,-116,-70,-90,-11,-66,-5,
        109,48,-110,92,-18,-60,-109,41,-105,-97,2,0,59 },
      /* next: */
      { 71,73,70,56,57,97,24,0,24,0,-128,0,0,0,0,0,-50,-49,-50,33,
        -7,4,1,0,0,1,0,44,0,0,0,0,24,0,24,0,64,2,54,-116,
        -113,-87,11,-101,-48,-100,108,80,-38,80,47,-42,38,-13,15,-122,8,21,77,-97,
        55,106,-24,89,-118,-18,11,-117,80,-21,-52,-10,-36,-47,15,-73,30,-6,-92,-5,
        41,122,-71,27,14,35,-68,28,99,-52,-90,-90,0,0,59 },
      /* zoom-in: */
      { 71,73,70,56,57,97,24,0,24,0,-128,0,0,0,0,0,-50,-49,-50,33,
        -7,4,1,0,0,1,0,44,0,0,0,0,24,0,24,0,64,2,62,-116,
        -113,-87,10,-36,-7,86,-120,11,80,105,-99,60,119,-10,-66,109,89,72,-118,37,
        104,68,-42,-70,114,38,50,-70,-91,-57,114,-15,-116,-25,57,123,-109,104,-22,-125,
        92,126,-97,15,-58,-58,83,29,-123,-116,87,-24,103,115,-76,116,82,-86,85,87, 0,0,59 },
      /* zoom-out: */
      { 71,73,70,56,57,97,24,0,24,0,-128,0,0,0,0,0,-50,-49,-50,33,
        -7,4,1,0,0,1,0,44,0,0,0,0,24,0,24,0,64,2,57,-116,
        -113,-87,10,-69,-19,2,116,96,-58,-54,-20,-117,73,-13,-21,125,-94,17,118,-41,
        81,-91,41,122,126,101,-90,-94,-40,72,-41,-74,-92,-66,12,-89,75,-30,-37,99,
        109,72,-71,-103,111,-8,107,-15,126,-98,-36,13,-10,-116,-46,10,0,59 },
      /* rotate: */
      { 71,73,70,56,57,97,24,0,24,0,-128,0,0,0,0,0,-50,-49,-50,33,
        -7,4,1,0,0,1,0,44,0,0,0,0,24,0,24,0,64,2,55,-116,
        -113,-87,10,-21,-67,-96,11,-96,78,42,89,70,-5,-102,-18,-123,81,101,-119,32,
        19,-119,28,-22,65,103,72,-106,-22,76,-73,-101,60,-67,107,-86,118,58,127,-8,
        -79,-126,53,31,-84,113,-6,-59,106,-52,-90,-45,80,0,0,59 }
      };

    buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 5));

    if (pspages.size() > 1) {
      previousButton = new JButton(new ImageIcon(buttonIcons[0]));
      previousButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            if (pagenr > 1) pagenr--;
            getRootPane().setDefaultButton(previousButton);
            if (pagenr < pspages.size()) nextButton.setEnabled(true);
            if (pagenr == 1) {
              previousButton.setEnabled(false);
              buttonPanel.getRootPane().setDefaultButton(nextButton);
              nextButton.requestFocus();
            }
            paint((Graphics2D)getGraphics());
          }
        }
      );
      previousButton.setEnabled(pagenr > 1);
      buttonPanel.add(previousButton);

      nextButton = new JButton(new ImageIcon(buttonIcons[1]));
      nextButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            if (pagenr < pspages.size()) pagenr++;
            getRootPane().setDefaultButton(nextButton);
            if (pagenr > 1) previousButton.setEnabled(true);
            if (pagenr == pspages.size()) {
              nextButton.setEnabled(false);
              buttonPanel.getRootPane().setDefaultButton(previousButton);
              previousButton.requestFocus();
            }
            paint((Graphics2D)getGraphics());
          }
        }
      );
      nextButton.setEnabled(pagenr < pspages.size());
      if (pagenr < pspages.size()) {
        getRootPane().setDefaultButton(nextButton);
        nextButton.requestFocus();
      } else {
        getRootPane().setDefaultButton(previousButton);
        previousButton.requestFocus();
      }
      haveDefaultButton = true;
      buttonPanel.add(nextButton);
    }

    if (doZoom) {
      zoominButton = new JButton(new ImageIcon(buttonIcons[2]));
      zoominButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            currentZoom *= zoomFactor;
            buttonPanel.getRootPane().setDefaultButton(zoominButton);
            zoominButton.requestFocus();
            paint((Graphics2D)getGraphics());
          }
        }
      );
      buttonPanel.add(zoominButton);
      if (!haveDefaultButton) {
        getRootPane().setDefaultButton(zoominButton);
        zoominButton.requestFocus();
        haveDefaultButton = true;
      }

      zoomoutButton = new JButton(new ImageIcon(buttonIcons[3]));
      zoomoutButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            currentZoom /= zoomFactor;
            buttonPanel.getRootPane().setDefaultButton(zoomoutButton);
            zoomoutButton.requestFocus();
            paint((Graphics2D)getGraphics());
          }
        }
      );
      buttonPanel.add(zoomoutButton);
    }

    if (doRotate) {
      rotateButton = new JButton(new ImageIcon(buttonIcons[4]));
      rotateButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            currentAngle += deltaAngle;
            buttonPanel.getRootPane().setDefaultButton(rotateButton);
            rotateButton.requestFocus();
            paint((Graphics2D)getGraphics());
          }
        }
      );
      buttonPanel.add(rotateButton);
      if (!haveDefaultButton) {
        getRootPane().setDefaultButton(rotateButton);
        rotateButton.requestFocus();
        haveDefaultButton = true;
      }
    }

  }


  void calculateOffsets(double deltaX, double deltaY) {
    AffineTransform transform = AffineTransform.getTranslateInstance((drawingPanel.getWidth()-1)*0.5,
                                                                     (drawingPanel.getHeight()-1)*0.5);
    transform.scale(currentZoom, currentZoom);
    transform.rotate(currentAngle);
    transform.translate(currentXOffset-(drawingPanel.getWidth()-1)*0.5,
                        currentYOffset-(drawingPanel.getHeight()-1)*0.5);
    try {
      transform = transform.createInverse();
    } catch(NoninvertibleTransformException nte) {
      return; // cannot really happen!
    }
    Point2D.Double delta = (Point2D.Double)transform.deltaTransform(new Point2D.Double(deltaX, deltaY), null);
    currentXOffset += delta.getX();
    currentYOffset += delta.getY();
  }


  public void paint(Graphics g) {
    super.paint(g);
    AffineTransform transform = AffineTransform.getTranslateInstance((drawingPanel.getWidth()-1)*0.5,
                                                                     (drawingPanel.getHeight()-1)*0.5);
    transform.scale(currentZoom, currentZoom);
    transform.rotate(currentAngle);
    transform.translate(currentXOffset-(drawingPanel.getWidth()-1)*0.5,
                        currentYOffset-(drawingPanel.getHeight()-1)*0.5);
    pspages.paint(drawingPanel, transform, pagenr);
  }


  public static byte getDebug() { return debug; }

  public static boolean isDebug() { return (debug != (byte)0); }

  public static void setDebug(boolean on) { debug = (on) ? (byte)9 : (byte)0; }

  public static void setDebug(byte level) { debug = level; }

  public String getAppletInfo() {
    return "PostScript file converted to Java by pstoedit\n" +
           "© Copyright 1998 - 1999 Wolfgang Glunz, wglunz35_AT_pstoedit.net\n" +
           "© Copyright parts: 2000 Gisbert W. Selke, gisbert@tapirsoft.de\n" +
           "Java2 driver: alpha test version, 17 Apr 2000";
  }


  public String[][] getParameterInfo() {
    String[][] info = {
                        {"zoom",   "string or 1..2 numbers", "display zoom buttons;\n " +
                                           "\t1st number (>0), if any: initial zoom factor (default: 0);\n" +
                                           "\t2nd number (>1), if any: zoom step (default: 1.2)" },
                        {"rotate", "string or 1..2 numbers", "display rotate button;\n " +
                                           "\t1st number, if any: initial rotation in degrees (default: 0);\n" +
                                           "\t2nd number, if any: angle increment (default: 90)" },
                        {"offset", "1..2 numbers", "initial x/y offset (default: 0 0)" },
                        {"page",   "number (>= 1)", "number of page to be shown initially (default: 1)" }
                      };
    return info;
  }


  Double[] parseParameters(String paramName, int nparams) {
    StringTokenizer tok;
    Double[] values = new Double[nparams];
    int i;

    if (getParameter(paramName) == null) return null;
    for (i=0; i<nparams; i++) values[i] = null;
    tok = new StringTokenizer(getParameter(paramName), " \t\n\r\f,;");
    i = 0;
    while (tok.hasMoreTokens() && (i < nparams)) {
      try {
        values[i] = Double.valueOf(tok.nextToken());
      } catch (NumberFormatException nfe) {
      } finally {
        i++;
      }
    }
    return values;
  }

}


// a font descriptor
class FontDescriptor {

  public String name;
  public int style;

  FontDescriptor(String name, int style) {
    this.name = name;
    this.style = style;
  }
};


// the base class for the drawing objects
class PSDrawingObject {

  Color c;

  public PSDrawingObject(Color c) { this.c = c; }

  public void paint(Graphics2D g)  {
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,      RenderingHints.VALUE_ANTIALIAS_ON);
    g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    g.setRenderingHint(RenderingHints.KEY_DITHERING,         RenderingHints.VALUE_DITHER_ENABLE);
    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,     RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
    g.setRenderingHint(RenderingHints.KEY_RENDERING,         RenderingHints.VALUE_RENDER_QUALITY);
    g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,   RenderingHints.VALUE_COLOR_RENDER_QUALITY);
    g.setColor(c);
  }

}


// derived class for text objects
class PSTextObject extends PSDrawingObject {

  Font   font = null;
  float  x = 0.0f, y = 0.0f, angle = 0.0f;
  String str = null;

  static FontDescriptor[] JavaFonts = {
    new FontDescriptor("Monospaced", Font.PLAIN),
    new FontDescriptor("Monospaced", Font.BOLD),
    new FontDescriptor("Monospaced", Font.ITALIC),
    new FontDescriptor("Monospaced", Font.BOLD + Font.ITALIC),
    new FontDescriptor("SansSerif",  Font.PLAIN),
    new FontDescriptor("SansSerif",  Font.BOLD),
    new FontDescriptor("SansSerif",  Font.ITALIC),
    new FontDescriptor("SansSerif",  Font.BOLD + Font.ITALIC),
    new FontDescriptor("Serif",      Font.PLAIN),
    new FontDescriptor("Serif",      Font.BOLD),
    new FontDescriptor("Serif",      Font.ITALIC),
    new FontDescriptor("Serif",      Font.BOLD + Font.ITALIC),
    new FontDescriptor("Symbol",     Font.PLAIN),
  };


  PSTextObject(Color c, String str, float x, float y, int fontNum,
               float fontSize, float angle) {
    this(c, str, x, y, JavaFonts[fontNum].name, JavaFonts[fontNum].style, fontSize, angle);
  }


  PSTextObject(Color c, String str, float x, float y, int fontNum, float fontSize) {
    this(c, str, x, y, JavaFonts[fontNum].name, JavaFonts[fontNum].style, fontSize, 0);
  }


  PSTextObject(Color c, String str, float x, float y, String fontName,
               int fontStyle, float fontSize, float angle) {
    super(c);
    this.str  = str;
    this.x    = x;
    this.y    = y;
    this.font = (new Font(fontName, fontStyle, 1)).deriveFont(fontSize);
    this.angle = (float)(Math.toRadians(-angle));
  }


  PSTextObject(Color c, String str, float x, float y, String fontName,
               int fontStyle, float fontSize) {
    this(c, str, x, y, fontName, fontStyle, fontSize, 0);
  }


  PSTextObject(Color c, String str, float x, float y, String fontName,
               int fontStyle, AffineTransform fontMatrix) {
    super(c);
    this.str  = str;
    this.x    = x;
    this.y    = y;
    this.font = (new Font(fontName, fontStyle, 1)).deriveFont(fontMatrix);
    this.angle = 0f;
  }


  PSTextObject(Color c, String str, float x, float y, int fontNum,
               AffineTransform fontMatrix) {
    this(c, str, x, y, JavaFonts[fontNum].name, JavaFonts[fontNum].style, fontMatrix);
  }


  public void paint(Graphics2D g) {
    super.paint(g);
    if (str != null) {
      g.setFont((font == null) ? new Font(JavaFonts[0].name, JavaFonts[0].style, 10) : font);
      if (angle == 0.0f) {
        g.drawString(str, x, y);
      } else {
        AffineTransform oldTransform = g.getTransform();
        g.rotate(angle, x, y);
        g.drawString(str, x, y);
        g.setTransform(oldTransform);
      }
    }
  }

}


// derived class for general paths (possibly including disjoint subpaths)
class PSPathObject extends PSDrawingObject {

  GeneralPath path = null;
  int nPoints=0;
  float lineWidth=1.0f, miterLimit=10.0f;
  float[] dash = null;
  float dashPhase = 0.0f;
  int lineCap=0, lineJoin=0, fillStroke=0;
  boolean isPolygon=false;


  PSPathObject(Color c, float lineWidth) {
    this(c, lineWidth, 0, 0, 1.0f, 0, false);
  }


  PSPathObject(Color c, float lineWidth, boolean isPolygon) {
    this(c, lineWidth, 0, 0, 1.0f, 0, isPolygon);
  }


  PSPathObject(Color c, float lineWidth, int lineCap, int lineJoin, float miterLimit, int fillStroke) {
    this(c, lineWidth, lineCap, lineJoin, miterLimit, fillStroke, false);
  }


  PSPathObject(Color c, float lineWidth, int lineCap, int lineJoin, float miterLimit, int fillStroke,
               float[] dash, float dashPhase) {
    this(c, lineWidth, lineCap, lineJoin, miterLimit, fillStroke, dash, dashPhase, false);
  }


  PSPathObject(Color c, float lineWidth, int lineCap, int lineJoin, float miterLimit,
               int fillStroke, boolean isPolygon) {
    this(c, lineWidth, lineCap, lineJoin, miterLimit, fillStroke, null, 0.0f, false);
  }


  PSPathObject(Color c, float lineWidth, int lineCap, int lineJoin, float miterLimit,
               int fillStroke, float[] dash, float dashPhase, boolean isPolygon) {
    super(c);
    path = new GeneralPath();
    this.lineWidth = (lineWidth >= 0.0f) ? lineWidth : 0.1f;
    switch(lineCap) {
      case 1 : this.lineCap = BasicStroke.CAP_ROUND;  break;
      case 2 : this.lineCap = BasicStroke.CAP_SQUARE; break;
      default: this.lineCap = BasicStroke.CAP_BUTT;   break;
    }
    switch(lineJoin) {
      case 1 : this.lineJoin = BasicStroke.JOIN_ROUND; break;
      case 2 : this.lineJoin = BasicStroke.JOIN_BEVEL; break;
      default: this.lineJoin = BasicStroke.JOIN_MITER; break;
    }
    this.miterLimit = (miterLimit >= 1.0f) ? miterLimit : 1.0f;
    this.fillStroke = ((fillStroke >= 0) && (fillStroke <= 2)) ? fillStroke : 0;
    path.setWindingRule((this.fillStroke == 2) ? GeneralPath.WIND_EVEN_ODD : GeneralPath.WIND_NON_ZERO);
    if (dash != null) {
      this.dash = new float[dash.length];
      System.arraycopy(dash, 0, this.dash, 0, dash.length);
      this.dashPhase = dashPhase;
    }
    this.isPolygon  = isPolygon;
  }


  PSPathObject(Color c, float lineWidth, int lineJoin, int fillStroke, Shape shape) {
    super(c);
    path = new GeneralPath(shape);
    this.lineWidth = (lineWidth >= 0.0f) ? lineWidth : 0.1f;
    this.lineCap = BasicStroke.CAP_BUTT;  // not really needed for rectangles
    switch(lineJoin) {
      case 1 : this.lineJoin = BasicStroke.JOIN_ROUND; break;
      case 2 : this.lineJoin = BasicStroke.JOIN_BEVEL; break;
      default: this.lineJoin = BasicStroke.JOIN_MITER; break;
    }
    this.miterLimit = 1.0f;               // not really needed for rectangles
    this.fillStroke = ((fillStroke >= 0) && (fillStroke <= 2)) ? fillStroke : 0;
    this.isPolygon  = true;               // does no harm
  }


  PSPathObject(Color c, float lineWidth, Shape shape) {
    this(c, lineWidth, 0, 0, shape);
  }


  public void moveTo(float x, float y) {
    path.moveTo(x, y);
    nPoints++;
  }


  public void lineTo(float x, float y) {
    if (nPoints == 0) {
      path.moveTo(x, y);
    } else {
      path.lineTo(x, y);
    }
    nPoints++;
  }


  public void curveTo(float x0, float y0, float x1, float y1, float x2, float y2) {
    if (nPoints == 0) moveTo(0, 0);
    path.curveTo(x0, y0, x1, y1, x2, y2);
    nPoints++;
  }


  public void closePath() { path.closePath(); }


  public void paint(Graphics2D g) {
    super.paint(g);
    if (isPolygon) closePath();
    g.setStroke((dash == null) ?
                new BasicStroke(lineWidth, lineCap, lineJoin, miterLimit) :
                new BasicStroke(lineWidth, lineCap, lineJoin, miterLimit, dash, dashPhase));
    if (fillStroke == 0) {
      g.draw(path);
    } else {
      g.fill(path);
    }
  }

}


// derived class for bitmap images
// -- not at all complete!
class PSImageObject extends PSDrawingObject {

  int imageType = 0;           // 0=colour image; 1=normal image; 2=image mask
  int width = 0;
  int height = 0;
  int bitsPerComponent = 8;
  int numberOfComponents = 1;
  int colourType = BufferedImage.TYPE_CUSTOM;
  boolean polarity = true;     // meaningful for image masks only
  AffineTransform transform;   // to convert between PostScript and Java coordinate systems
  byte[] data = null;          // the image data
  BufferedImage image = null;  // the rendered (pixel) image


  PSImageObject(int width, int height, int bitsPerComponent, int numberOfComponents,
                int type, boolean polarity, AffineTransform transform) {
    super(Color.black); // Dummy call
    this.width = width;
    this.height = height;
    this.numberOfComponents = numberOfComponents;
    switch (numberOfComponents) {
      case 1 : this.colourType = BufferedImage.TYPE_BYTE_GRAY; break;
      case 3 : this.colourType = BufferedImage.TYPE_3BYTE_BGR; break;   // should be RGB???  TODO!!
      case 4 : this.colourType = BufferedImage.TYPE_4BYTE_ABGR; break;  // no, this should be CMYK -- TODO!!
      default: this.colourType = BufferedImage.TYPE_BYTE_GRAY; break;
    }
    this.bitsPerComponent = bitsPerComponent;
    this.imageType = type;
    this.polarity = polarity;
    this.transform = new AffineTransform(transform);
  }


  PSImageObject(int width, int height, int bitsPerComponent, int numberOfComponents,
                int type, boolean polarity, AffineTransform transform, byte[] data) {
    this(width, height, bitsPerComponent, numberOfComponents, type, polarity, transform);
    this.data = new byte[data.length];
    System.arraycopy(data, 0, this.data, 0, data.length);
    setRasterData();
  }


  PSImageObject(int width, int height, int bitsPerComponent, int numberOfComponents,
                int type, boolean polarity, AffineTransform transform, String resourceName) {
    this(width, height, bitsPerComponent, numberOfComponents, type, polarity, transform);

    int nColumns   = (width*numberOfComponents*bitsPerComponent+7)/8;
    int dataLength = nColumns*height;
    DataInputStream dataFile = null;

    data = new byte[dataLength];
    try {
      dataFile = new DataInputStream(new BufferedInputStream(new FileInputStream(resourceName)));
      if (PSDrawing.getDebug() > 1) System.err.println("Reading image data from " + resourceName);
    } catch (Exception e) {
      if (PSDrawing.getDebug() > 0) System.err.println("No image data found for file " + resourceName);
    };

    if (dataFile == null) {
      try {
        dataFile = new DataInputStream(new java.util.zip.GZIPInputStream(new BufferedInputStream(
                                       new FileInputStream(resourceName + ".gz"))));
        if (PSDrawing.getDebug() > 1) System.err.println("Reading image data from " + resourceName + ".gz");
      } catch (Exception e) {
        if (PSDrawing.getDebug() > 0) System.err.println("No image data found for file " + resourceName + ".gz");
      };
    }

    if (dataFile == null) {
      try {
        java.net.URL url = (new File(resourceName)).toURL();
        java.net.URLConnection connection = url.openConnection();
        // How about handling JarURLConnection, too??!!
        dataFile = new DataInputStream(new BufferedInputStream(connection.getInputStream()));
        if (PSDrawing.getDebug() > 1) System.err.println("Reading image data from " + url);
      } catch (Exception e) {
        if (PSDrawing.getDebug() > 0) System.err.println("No image data found for URL " + resourceName);
      }
    }

    if (PSDrawing.getDebug() > 1) System.err.println("width: " + width + ", height: " + height +
                                                     ", bits/component: " + bitsPerComponent +
                                                     ", nComponents: " + numberOfComponents +
                                                     ", data length: " + dataLength);
    try {
      dataFile.readFully(data);
      if (PSDrawing.getDebug() > 1) System.err.println(" " + dataLength + " read");
      setRasterData();
    } catch (IOException eofe) {
      for (int i=0; i<dataLength; i++) data[i] = 0;
      setRasterData();
      // produce an error image instead??
      System.err.println("Error trying to read image data " + resourceName);
    }
  }


  void setRasterData() {
    DataBuffer dataBuffer = null;
    Raster raster = null;
    // Check whether preprocessing of the data is necessary:
    // Currently, everything is mapped to 8 bit grayscale.
    // Details of colour representation and conversion between a number of external
    // colour spaces/packed data formats and Java-internal representations still
    // need to be worked out.
    // CMYK not supported at all -- anything might happen!
    // This is currently a big mess.
    int temp1, temp2, temp3, row, col, offset, i, k;
    byte[] oldData;

    // inflate data to 8 bits per component, if necessary:
    if ((bitsPerComponent == 1) && (numberOfComponents == 1)) {
      oldData = new byte[data.length];
      System.arraycopy(data, 0, oldData, 0, data.length);
      data = new byte[width*height];
      if (PSDrawing.getDebug() > 1) System.err.println("bits per component: " + bitsPerComponent +
                                                       ", numberOfComponents: " + numberOfComponents +
                                                       ", old data length: " + oldData.length +
                                                       ", new data length: " + data.length);
      k = 0;
      for (row=0; row<height; row++) {
        offset = row*(int)((width*bitsPerComponent*numberOfComponents+7)/8);
        for (col=0; col<(width+7)/8; col++) {
          temp1 = (int)oldData[offset+col];
          if (temp1 < 0) temp1 += 256;
          for (i=0; i<8; i++) {
            if (8*col+i < width) {
              data[k++] = (byte)(((temp1 & 0x80) == 0) ? 0 : -1);
            }
            temp1 <<= 1;
          }
        }
      }
      colourType = BufferedImage.TYPE_BYTE_GRAY;
      bitsPerComponent = 8;
    }
    if ((bitsPerComponent == 4) && (numberOfComponents == 1)) {
      oldData = new byte[data.length];
      System.arraycopy(data, 0, oldData, 0, data.length);
      data = new byte[width*height];
      if (PSDrawing.getDebug() > 1) System.err.println("bits per component: " + bitsPerComponent +
                                                       ", numberOfComponents: " + numberOfComponents +
                                                       ", old data length: " + oldData.length +
                                                       ", new data length: " + data.length);
      k = 0;
      for (row=0; row<height; row++) {
        offset = row*(int)((width*bitsPerComponent*numberOfComponents+7)/8);
        for (col=0; col<width; col++) {
          temp1 = (int)oldData[offset++];
          if (temp1 < 0) temp1 += 256;
          temp3 = temp1 >> 4;
          temp3 |= temp3 << 4;
          if (temp3 > 127) temp3 -= 256;
          data[k++] = (byte)temp3;
          temp3 = temp1 & 0x0F;
          temp3 |= temp3 << 4;
          if (temp3 > 127) temp3 -= 256;
          data[k++] = (byte)temp3;
        }
      }
      colourType = BufferedImage.TYPE_BYTE_GRAY;
      bitsPerComponent = 8;
    }
    /*
    if ((bitsPerComponent == 4) && (numberOfComponents == 3)) {
    // Test für echte Farben!
      oldData = new byte[data.length];
      System.arraycopy(data, 0, oldData, 0, data.length);
      data = new byte[width*height*4];
      if (PSDrawing.getDebug() > 1) System.err.println("bits per component: " + bitsPerComponent +
                                                       ", numberOfComponents: " + numberOfComponents +
                                                       ", old data length: " + oldData.length +
                                                       ", new data length: " + data.length);
      k = 0;
      for (row=0; row<height; row++) {
        offset = row*(int)((width*bitsPerComponent*numberOfComponents+7)/8);
        for (col=0; col<width; col+=2) {
          temp1 = (int)oldData[offset++];
          if (temp1 < 0) temp1 += 256;
          temp3 = temp1 >> 4;
          temp3 |= temp3 << 4;
          if (temp3 > 127) temp3 -= 256;
          data[k++] = (byte)temp3;
          temp3 = temp1 & 0x0F;
          temp3 |= temp3 << 4;
          if (temp3 > 127) temp3 -= 256;
          data[k++] = (byte)temp3;
          temp1 = (int)oldData[offset++];
          if (temp1 < 0) temp1 += 256;
          temp3 = temp1 >> 4;
          temp3 |= temp3 << 4;
          if (temp3 > 127) temp3 -= 256;
          data[k++] = (byte)temp3;
          temp3 = temp1 & 0x0F;
          temp3 |= temp3 << 4;
          if (temp3 > 127) temp3 -= 256;
          data[k++] = (byte)temp3;
          temp1 = (int)oldData[offset++];
          if (temp1 < 0) temp1 += 256;
          temp3 = temp1 >> 4;
          temp3 |= temp3 << 4;
          if (temp3 > 127) temp3 -= 256;
          data[k++] = (byte)temp3;
          temp3 = temp1 & 0x0F;
          temp3 |= temp3 << 4;
          if (temp3 > 127) temp3 -= 256;
          data[k++] = (byte)temp3;
        }
      }
      colourType = BufferedImage.TYPE_3BYTE_BGR;
      bitsPerComponent = 8;
    }
    */
    if ((bitsPerComponent == 4) && (numberOfComponents == 3)) {
      oldData = new byte[data.length];
      System.arraycopy(data, 0, oldData, 0, data.length);
      data = new byte[width*height];
      if (PSDrawing.getDebug() > 1) System.err.println("bits per component: " + bitsPerComponent +
                                                       ", numberOfComponents: " + numberOfComponents +
                                                       ", old data length: " + oldData.length +
                                                       ", new data length: " + data.length);
      k = 0;
      for (row=0; row<height; row++) {
        offset = row*(int)((width*bitsPerComponent*numberOfComponents+7)/8);
        for (col=0; col<width; col+=2) {
          temp1 = (int)oldData[offset++];
          temp2 = (int)oldData[offset++];
          if (temp1 < 0) temp1 += 256;
          if (temp2 < 0) temp2 += 256;
          temp3 = ((temp1 >> 4) + (temp1 & 0x0F) + (temp2 >> 4) + 2)/3;
          temp3 |= temp3 << 4;
          if (temp3 > 127) temp3 -= 256;
          data[k++] = (byte)temp3;
          temp1 = (int)oldData[offset++];
          if (temp1 < 0) temp1 += 256;
          temp3 = ((temp2 & 0x0F) + (temp1 >> 4) + (temp1 & 0x0F) + 2)/3;
          temp3 |= temp3 << 4;
          if (temp3 > 127) temp3 -= 256;
          data[k++] = (byte)temp3;
        }
      }
      colourType = BufferedImage.TYPE_BYTE_GRAY;
      bitsPerComponent = 8;
    }
    if (PSDrawing.getDebug() > 1) System.err.println("width: " + width + ", height: " + height +
                                                     ", bits/component: " + bitsPerComponent +
                                                     ", colourType: " + colourType +
                                                     ", data length: " + data.length);
    image = new BufferedImage(width, height, colourType);
    dataBuffer = new DataBufferByte(data, data.length);
    raster = Raster.createPackedRaster(dataBuffer, width, height, bitsPerComponent, null);
    image.setData(raster);
  }


  public void paint(Graphics2D g) {
    super.paint(g);
    g.drawRenderedImage(image, transform);
  }

}


// describes a single page
class PageDescription extends Vector {

  public void paint(JComponent drawingPanel, AffineTransform t) {
    Graphics2D g = (Graphics2D)(drawingPanel.getGraphics());
    g.setColor(Color.white); // rather set to transparent??
    g.fill(drawingPanel.getBounds());
    g.setTransform(t);
    if (PSDrawing.getDebug() > 2) System.err.println("transform is now " + t);
    for (Enumeration e=elements(); e.hasMoreElements(); ) {
      ((PSDrawingObject)(e.nextElement())).paint(g);
    }
  }

}


// describes the set of all pages
class PSPages extends Vector {

  public void paint(JComponent drawingPanel, AffineTransform t, int pageNumber) {
    if (PSDrawing.getDebug() > 1) System.err.println("showing page #" + pageNumber + " " + drawingPanel.getBounds());
    if (pageNumber <= size()) ((PageDescription)get(pageNumber-1)).paint(drawingPanel, t);
  }

}

