/*
TODO:
        Make creation of Hcube, Ring, etc more streamlined
        Create grouped objects, particularly for text
        Special selection for text, with double click.
        Selectors should have priority for events
        Launch an Item from its selected text name. 
        Incorporate Quikwriting variant
        Put in more active applets (eg: planet)
*/

import java.awt.*;
import java.awt.image.*;
import java.util.*;

public class Zoom5 extends BufferedApplet
{
   WhiteBoard whiteBoard = new WhiteBoard(this);

   public boolean mouseDown(Event e, int x, int y) { return whiteBoard.mouseDown(e, x, y); }
   public boolean mouseDrag(Event e, int x, int y) { return whiteBoard.mouseDrag(e, x, y); }
   public boolean mouseUp(Event e, int x, int y) { return whiteBoard.mouseUp(e, x, y); }
   public boolean keyUp(Event e, int key) { return whiteBoard.keyUp(e, key); }
   public void render(Graphics g) {
      if (! damage && ! whiteBoard.isDamage)
         return;
      whiteBoard.width  = bounds().width;
      whiteBoard.height = bounds().height;
      whiteBoard.render(g);
   }
}

class WhiteBoard extends Item
{
   WhiteBoard(java.applet.Applet applet) {
      super();
      this.applet = applet;
/*
      Item item = new Hcube();
      item.parent = this;
      item.color = color;
      item.init(width/2, height/2, 1);
      items.addElement(item);
*/
   }

   java.applet.Applet applet;
   Vector selectors = new Vector();
   Item activeItem = null;
   Selector activeSelector = null;
   int nChars = 0, nLines = 0, x, y, border;
   Stroke s;
   boolean isTap = false, tapOnSelector = false;
   int count = 0, x1 = -1, y1;

///////////// CONVENIENT ACCESS FUNCTIONS FOR COMMON ITEM TYPES

   Stroke stroke(int n)     { return (Stroke)item(n); }
   Selector selector(int n) { return (Selector)item(n); }

   Stroke lastStroke() {
      return stroke(lastStrokeIndex());
   }
   int lastStrokeIndex() {
      for (int i = items.size()-1 ; i >= 0 ; i--)
         if (item(i) instanceof Stroke)
            return i;
      return -1;
   }
   void removeLastStroke(int n) {
      int i = lastStrokeIndex();
      if (i >= 0)
         items.removeElementAt(i);
   }

///////////// SUPPORT FOR ZOOMING FROM JAVASCRIPT BUTTONS

   String name = "";
   int jsX, jsY; // this is the center-of-zoom, when zooming from javascript
   void jsSet(int x, int y) { jsX = x; jsY = y; name = ""; }
   public void up()   { zoomSurface(jsX, jsY, 2); }
   public void down() { zoomSurface(jsX, jsY,.5); }

///////////// ZOOMING LOGIC

   double ZOOM = 1;

   public void zoomSurface(int x, int y, double z) {
      if (z != 1) {
         S /= z;
         X += (x - X) * (1 - 1/z);
         Y += (y - Y) * (1 - 1/z);
         nChars = nLines = 0;
         isDamage = true;
      }
   }

   public void zoomSelector(Selector sel, int x, int y, double z) {
      double dx, dy;
      dx = (x - X) * (1 - z);
      dy = (y - Y) * (1 - z);
      sel.X += dx * sel.S / S;
      sel.Y += dy * sel.S / S;
      sel.S *= z;
      for (int i = 0 ; i < sel.items.size() ; i++) {
         Item s = sel.item(i);
         s.X += dx * s.S / S;
         s.Y += dy * s.S / S;
         s.S *= z;
      }
   }

///////////// EVENT HANDLERS

   boolean setColor = false;
   boolean panOrZoomSurface = false, zoomSurface = false, panSurface = false, panSelector = false;
   int px = 0, py = 0, zy, nd;

// *************** MOUSE DOWN

   public boolean mouseDown(Event e, int x, int y) { // begin a new stroke
      activeItem = null;
      nChars = nLines = 0;
      px = x;
      py = y;
      nd = 0;

      for (int j = items.size()-1 ; j >= 0 ; j--) {
         Item item = item(j);
         if (!(item instanceof Stroke)) {
            int ix = item.xFromScreen(x);
            int iy = item.yFromScreen(y);
            if (item.handles(ix, iy)) {

// starting to drag within a selector moves that selector

               if (item instanceof Selector) {
                  activeSelector = (Selector)item;
                  activeSelector.sticky = false;
                  panSelector = true;
                  ZOOM = 1;
                  count = 0;
                  return true;
               }

// check for procedural objects

               else if (item.mouseDown(e, ix, iy)) {
                  activeItem = item;
                  if (activeItem.isDamage) isDamage = true;
                  activeItem.isDamage = false;
                  return true;
               }
            }
         }
      }

// tap creates a new selector

      if (isTap) {
         activeSelector = new Selector();
         activeSelector.parent = this;
         activeSelector.init((x-X)/S, (y-Y)/S, 1);
         items.addElement(activeSelector);
      }

// select a color from the color palette

      else if (y >= height-border && x >= width/2-3*border && x < width/2+3*border) {
         if (x < width/2-border)
            color = Color.white;
         else if (x < width/2)
            color = Color.red;
         else if (x < width/2+border)
            color = Color.green.darker();
         else if (x < width/2+2*border)
            color = Color.blue;
         else
            color = Color.black;
         setColor = true;
      }

// starting to drag on the border drags the whole surface

      else if (x < border || y < border || x >= width-border || y >= height-border) {
         panOrZoomSurface = true;
         panSurface = zoomSurface = false;
         ZOOM = 1;
      }

// default: start drawing a new stroke

      else {
         items.addElement(s = new Stroke());
         s.parent = this;
         s.color = color;
         s.fat = (color == Color.white ? 32 : 4);
         s.S = S;
         s.addPoint(x, y);
      }
      return true;
   }

// *************** MOUSE DRAG

   public boolean mouseDrag(Event e, int x, int y) { // add a point to the stroke

      nd++;
      nChars = nLines = 0;

      if (activeItem != null) {
         activeItem.mouseDrag(e, activeItem.xFromScreen(x), activeItem.yFromScreen(y));
         if (activeItem.isDamage) isDamage = true;
         activeItem.isDamage = false;
      }

      else if (setColor)
         ;

// dragging after a tap rubber-bands the new selector

      else if (isTap) {
         count++;
         activeSelector.set(px, py, x, y);
         isDamage = true;
         return true;
      }

// dragging a selected region

      else if (panSelector) {
         count++;
         Selector sel = activeSelector;
         if (sel.loX() + (x-px) < border/2        || sel.loY() + (y-py) < border/2 ||
             sel.hiX() + (x-px) >= width-border/2 || sel.hiY() + (y-py) >= height-border/2)
            sel.sticky = true;
         else {
            sel.X += (px - x) * sel.S / S;
            sel.Y += (py - y) * sel.S / S;
            for (int i = 0 ; i < sel.items.size() ; i++) {
               Item s = sel.item(i);
               s.X += (px - x) * s.S / S;
               s.Y += (py - y) * s.S / S;
            }
            sel.sticky = sel.loX() < border          || sel.loY() < border ||
                         sel.hiX() >= width - border || sel.hiY() >= height - border;
            if (ZOOM < 1 || (sel.hiX()-sel.loX()) * (sel.hiY()-sel.loY()) * ZOOM >= 4*border*border)
               zoomSelector(sel, x, y, ZOOM);
         }
         isDamage = true;
         px = x;
         py = y;
         return true;
      }

// dragging the entire surface

      else if (panOrZoomSurface) {
        
         if (zoomSurface || (!panSurface && (x < border || x > width-border) && Math.abs(y-py) > Math.abs(x-px))) {
            zoomSurface = true;
            panSurface = false;
            ZOOM = y > py ? 1/1.03 : 1.03;
            zoomSurface(width/2, height/2, ZOOM);
            for (int j = 0 ; j < items.size() ; j++)
               if (item(j) instanceof Selector) {
                  Selector sel = selector(j);
                  if (sel.sticky) {
                     zoomSelector(sel, width/2, height/2, 1/ZOOM);
                  }
               }
         }
         else {
            panSurface = true;
            zoomSurface = false;
            X += x - px;
            Y += y - py;
            for (int j = 0 ; j < items.size() ; j++)
               if (item(j) instanceof Selector) {
                  Selector sel = selector(j);
                  if (sel.sticky) {
                     sel.X += (x - px) * sel.S / S;
                     sel.Y += (y - py) * sel.S / S;
                     for (int i = 0 ; i < sel.items.size() ; i++) {
                        Item s = sel.item(i);
                        s.X += (x - px) * s.S / S;
                        s.Y += (y - py) * s.S / S;
                     }
                  }
               }
         }
         isDamage = true;
         px = x;
         py = y;
         return true;
      }

      else {

// checking whether we've entered a selected region from the top or bottom

         activeSelector = null;
         for (int j = 0 ; j < items.size() ; j++)
            if (item(j) instanceof Selector) {
               Selector sel = selector(j);

               if (sel.contains(x, y) && (py < sel.loY() || py > sel.hiY())) {
                  removeLastStroke(0);
                  ZOOM = (py < sel.loY()) ? 1.02 : 1/1.02; //top=shrink, bottom=drag
                  activeSelector = sel;
                  panSelector = true;
                  px = x;
                  py = y;
                  isDamage = true;
                  return true;
               }
            }

// otherwise, dragging extends the current stroke

         s.addPoint(x, y);
      }
      isDamage = true;
      return true;
   }

// *************** MOUSE UP

   public boolean mouseUp(Event e, int x, int y) {

      jsSet(x, y);

// if we had tapped on a selector before, and it's not sticky, then just delete it

      if (tapOnSelector) {
         if (count < 2) {
            items.removeElement(activeSelector);
            activeSelector = null;
         }
         panSelector = tapOnSelector = false;
         isDamage = true;
         return true;
      }

      if (panSelector) {
         if (count < 2) {
            tapOnSelector = true;
            select(activeSelector);
         }
         panSelector = false;
      }
      else if (panOrZoomSurface)
         panOrZoomSurface = false;
      else if (setColor)
         setColor = false;

      else if (activeItem != null) {
         activeItem.mouseUp(e, activeItem.xFromScreen(x), activeItem.yFromScreen(y));
         if (activeItem.isDamage) isDamage = true;
         activeItem.isDamage = false;
         activeItem = null;
      }

// finished dragging over selector

      else if (isTap) {

         isTap = false;
         Selector sel = activeSelector;

// if this was another tap - then make a small touch box

         if (count < 2) {
            sel.set(px-border/2, py-border/2, px+border/2, py+border/2);
            sel.isTouchBox = true;
            select(sel);
         }

// else if the area was too small to be useful, then don't make a selection box

         else if (sel.hiX() - sel.loX() < border && sel.hiY() - sel.loY() < border)
            items.removeElement(sel);

// otherwise we must select all items that lie completely within this box

         else
            select(sel);

         activeSelector = null;
         isDamage = true;
         return true;
      }

      else if (items.size() == 0)
         return true;

// IF THIS WAS A TAP

      else if (items.size() > 0 && lastStroke().k <= 6) {
         removeLastStroke(3);

// tap on background to indicate we're about to define a new selector

         if (! isTap) {
            count = 0;
            isTap = true;
         }

         isDamage = true;            
      }

// IF THIS WAS A STROKE

      else {

// for hand-drawn strokes: end the stroke

         lastStroke().end();

// Check for gestures involving selection boxes

// compute first point in stroke in screen coords

         int x0 = s.a(0);
         int y0 = s.a(1);
         int xn = s.a(s.k-2);
         int yn = s.a(s.k-1);

// compute stroke bounds in screen coords

         int loX = s.loX();
         int loY = s.loY();
         int hiX = s.hiX();
         int hiY = s.hiY();

         for (int j = 0 ; j < items.size() ; j++)
            if (item(j) instanceof Selector) {
               Selector sel = selector(j);

// stroke inward from either side - duplicate

               if (y0 > sel.loY() && y0 < sel.hiY() && yn > sel.loY() && yn < sel.hiY() &&
                   (x0 < sel.loX() && xn >= sel.loX() || x0 >= sel.hiX() && xn < sel.hiX())) {
                  removeLastStroke(9);
                  Selector sel2 = (Selector)sel.clone();
                  items.addElement(sel2);
                  sel2.items = new Vector(); 
                  Item item;
                  for (int i = 0 ; i < sel.items.size() ; i++) {
                     item = (Item)sel.item(i).clone();
                     items.addElement(item);
                     sel2.items.addElement(item);
                  }
                  isDamage = true;
                  return true;
               }

// stroke through from side and back again deletes selector and all its contents

               else if (y0 > sel.loY() && y0 < sel.hiY() && yn > sel.loY() && yn < sel.hiY() &&
                   (x0 < sel.loX() && xn < sel.loX() || x0 >= sel.hiX() && xn >= sel.hiX())) {
                  removeLastStroke(8);
                  if (loX < sel.loX() && hiX >= sel.hiX()) {
                     for (int i = 0 ; i < sel.items.size() ; i++)
                        items.removeElement(sel.item(i));
                     items.removeElement(sel);
                  }
                  isDamage = true;
                  return true;
               }

// stroke through box from left to down - convert to surroundBox

               else if (loY > sel.loY() && hiY < sel.hiY() && x0 > sel.hiX() && loX < sel.loX()) {
                  removeLastStroke(7);
                  sel.isTouchBox = false;
                  select(sel);
                  isDamage = true;
                  return true;
               }

// stroke through box from left to up - convert to touchBox

               else if (loY > sel.loY() && hiY < sel.hiY() && x0 < sel.loX() && hiX > sel.hiX()) {
                  removeLastStroke(6);
                  sel.isTouchBox = true;
                  select(sel);
                  isDamage = true;
                  return true;
               }

            }
      }
      return true;
   }

// *************** KEY UP

   public boolean keyUp(Event e, int key) { // key-up event
      isTap = false;
      switch (key) {
      case '\n':
         nLines++;
         nChars = 0;
         name = "";
         break;
      case 27:
         for (int i = 0 ; i < name.length() ; i++)
            items.removeElementAt(items.size()-1);
         String className = name;
         name = "";
         try {
         try {
         try {
            Item item = (Item)Class.forName(className).newInstance();
            item.parent = this;
            item.color = color;
            item.init((jsX-X)/S, (jsY-Y)/S, 1/S);
            items.addElement(item);
         } catch (InstantiationException ex) { System.out.println(ex); }
         } catch (IllegalAccessException ex) { System.out.println(ex); }
         } catch (ClassNotFoundException ex) { System.out.println(ex); }
         break;
      case '\b':
         if (nChars > 0) {
            removeLastStroke(12);
            nChars--;
         }
         break;
      default:
         if ( !(key >= 0 && key < Stroke.font.length && Stroke.font[key] != null) )
            key = 0;
         items.addElement(s = new Stroke());
         s.parent = this;
         s.init(-8 * nChars++ - jsX + X, -16 * nLines - jsY + Y, S);
         if (key > 32 && key < 128)
            name += (char)key;
         s.color = color;
         s.a = Stroke.font[key];
         s.computeBounds();
         tryToSelect(activeSelector, lastStroke());
      }
      isDamage = true;
      return true;
   }

///////////// STROKE SELECTION LOGIC

   void select(Selector sel) {
      sel.items = new Vector(); 
      for (int i = 0 ; i < items.size() ; i++)
         if (! (item(i) instanceof Selector))
            tryToSelect(sel, item(i));
   }

   void tryToSelect(Selector sel, Item s) {
      if (sel == null)
         return;
      boolean isSelected = false;
      int loX = s.loX();
      int loY = s.loY();
      int hiX = s.hiX();
      int hiY = s.hiY();
      if (sel.isTouchBox ? s.touches(sel)
                         : sel.contains(loX, loY) && sel.contains(hiX, hiY))
         sel.items.addElement(s);
   }

///////////// RENDERING THE IMAGE

   public void render(Graphics g) {

      if (! isDamage)
         return;
      isDamage = false;

// clear the background

      border = height/20;

      g.setColor(Color.white);
      g.fillRect(border, border, width-2*border, height-2*border);
      g.setColor(Color.black);

// draw the selectors

      for (int j = 0 ; j < items.size() ; j++)
         if (item(j) instanceof Selector)
            selector(j).render(g);

// render all the items

      for (int i = 0 ; i < items.size() ; i++) {
         Item s = item(i);
         if (! (s instanceof Selector)) {
/*
            int w = (int)(s.hiX() - s.loX());
            int h = (int)(s.hiY() - s.loY());
            if (s.im == null || s.imWidth != w) {
               s.imWidth = w;
               s.im = applet.createImage(w, h);
            }
*/
            s.render(g);
         }
      }

// draw the border

      g.setColor(Color.gray);
      g.fillRect(0, 0, width, border);
      g.fillRect(0, height-border, width, border);
      g.fillRect(0, 0, border, height);
      g.fillRect(width-border, 0, border, height);

      g.setColor(Color.white);
      g.fillRect(width/2-3*border+1, height-border+1, 2*border-2, border-2);
      g.setColor(Color.red);
      g.fillRect(width/2-border+1, height-border+1, border-2, border-2);
      g.setColor(Color.green.darker());
      g.fillRect(width/2+1, height-border+1, border-2, border-2);
      g.setColor(Color.blue);
      g.fillRect(width/2+border+1, height-border+1, border-2, border-2);
      g.setColor(Color.black);
      g.fillRect(width/2+2*border+1, height-border+1, border-2, border-2);

      g.setColor(Color.gray.brighter());
      g.drawLine(0, 0, width, 0);
      g.drawLine(0, 0, 0, height);
      g.drawLine(border, height-border, width-border, height-border);
      g.drawLine(width-border, border, width-border, height-border);
      g.setColor(Color.black);
      g.drawLine(border-1, border-1, width-border, border-1);
      g.drawLine(border-1, border-1, border-1, height-border);
      g.drawLine(0, height-1, width, height-1);
      g.drawLine(width-1, 0, width-1, height);

// show the zoom level in the bottom-left corner

      g.drawString("S=" + S, 2, height-2);
   }
}

class Selector extends Item {
   static Color selectColor = new Color(240, 220, 120);
   static Color stickyColor = new Color(192, 176,  96);
   boolean isTouchBox = false, sticky = false;

   public void init(double x, double y, double s) {
      loX = hiX = x;
      loY = hiY = y;
      S = s;
   }

   void set(double x1, double y1, double x2, double y2) {
      x1 = xFromParent(x1);
      y1 = yFromParent(y1);
      x2 = xFromParent(x2);
      y2 = yFromParent(y2);
      loX = Math.min(x1, x2);
      hiX = Math.max(x1, x2);
      loY = Math.min(y1, y2);
      hiY = Math.max(y1, y2);
   }

   public void render(Graphics g) {
      int x1 = loX();
      int y1 = loY();
      int x2 = hiX();
      int y2 = hiY();
      g.setColor(sticky ? stickyColor : selectColor);
      g.fillRect(x1, y1, x2-x1, y2-y1);
      g.setColor((sticky ? stickyColor : selectColor).brighter());
      g.drawLine(x1, y1, x2, y1);
      g.drawLine(x1, y1, x1, y2);
      g.setColor(Color.black);
      g.drawLine(x1, y2, x2, y2);
      g.drawLine(x2, y1, x2, y2);
      if (isTouchBox) {
         g.drawLine(x1+1, y1+1, x2-1, y1+1);
         g.drawLine(x1+1, y1+1, x1+1, y2-1);
         g.setColor((sticky ? stickyColor : selectColor).brighter());
         g.drawLine(x1+1, y2-1, x2-1, y2-1);
         g.drawLine(x2-1, y1+1, x2-1, y2-1);
      }
   }
}

class Stroke extends Item {
   static final int NULL = -100000;
   static int tmpbuf[] = new int[10000]; // maximum coords in any one drawn line
   int a[], k = 0;
   double fat = 1;

   void init(double x, double y, double s) {
      X = x;
      Y = y;
      S = s;
   }

   int a(int j) { return j%2 == 0 ? xToScreen(a[j]) : yToScreen(a[j]); }

   void addPoint(int x, int y) { // extend the currently drawn line by one point
      x = xFromScreen(x);
      y = yFromScreen(y);
      if (k == 0)
         a = tmpbuf;
      a[k+2] = NULL;   // always pad the buffer with NULL
      a[k  ] = x;
      a[k+1] = y;
      loX = Math.min(loX, x);
      loY = Math.min(loY, y);
      hiX = Math.max(hiX, x);
      hiY = Math.max(hiY, y);
      k += 2;
   }

   void end() {
      if (a == tmpbuf)
         a = copyPoints();
   }
   
   int[] copyPoints() {
      int b[] = new int[k+3];
      for (int j = 0 ; j < k+3 ; j++)
         b[j] = a[j];
      return b;
   }

   void computeBounds() {
      for (int i = 0 ; a[2*i] != NULL ; i++) {
         int x = a[2*i];
         int y = a[2*i+1];
         if (y >= 0) {
            loX = Math.min(loX, x);
            loY = Math.min(loY, y);
            hiX = Math.max(hiX, x);
            hiY = Math.max(hiY, y);
         }
      }
   }

   boolean touches(Item s) {
      if (super.touches(s))
         for (int j = 0 ; a[j+2] != NULL ; j += 2)
            if (a[j+1] != NULL && a[j+3] != NULL && s.contains(a(j), a(j+1)))
               return true;
      return false;
   }

   int sx[] = new int[4];
   int sy[] = new int[4];

   public void render(Graphics g) {
      double mag = parent.S / S;
      int x1 = loX();
      int y1 = loY();
      int x2 = hiX();
      int y2 = hiY();
      if (x1 >= parent.width || y1 >= parent.height || x2 < 0 || y2 < 0)
         return;
      if (x2-x1 <= 1 && y2-y1 <= 1) {
         g.fillRect(x1, y1, 1, 1);
         return;
      }
      int w = (int)(mag * fat);
      g.setColor(color);
      if (w > 2) {
         x1 = xToScreen(a[0]);
         y1 = yToScreen(a[1]);
         g.fillOval(x1-w/2, y1-w/2, w-1, w-1);
      }
      for (int j = 0 ; a[j+2] != NULL ; j += 2)
         if (a[j+1] != NULL && a[j+3] != NULL) {
            x1 = a(j  );
            y1 = a(j+1);
            x2 = a(j+2);
            y2 = a(j+3);
            if (w <= 1) {
               if (x1 != x2 || y1 != y2)
                  g.drawLine(x1, y1, x2, y2);
            }
            else if (w < 4) {
               computeThickLine(x1, y1, x2, y2, 2, sx, sy);
               g.fillPolygon(sx, sy, 4);
            }
            else {
               computeThickLine(x1, y1, x2, y2, w, sx, sy);
               if (isVisible(sx, sy))
                  g.fillPolygon(sx, sy, 4);  // draw a magnified segment
               g.fillOval(x2-w/2, y2-w/2, w-1, w-1);
            }
         }
   }

   boolean isVisible(int[] x, int[] y) {
      int loX = 10 * parent.width;
      int loY = 10 * parent.height;
      int hiX = -10 * parent.width;
      int hiY = -10 * parent.height;
      for (int j = 0 ; j < x.length ; j++) {
         loX = Math.min(loX, x[j]);
         loY = Math.min(loY, y[j]);
         hiX = Math.max(hiX, x[j]);
         hiY = Math.max(hiY, y[j]);
      }
      return loX < parent.width && loY < parent.height && hiX >= 0 && hiY >= 0;
   }

   void computeThickLine(int ax, int ay, int bx, int by, int w, int[] x, int[] y) {
      double lx = bx - ax;
      double ly = by - ay;
      double len = Math.sqrt(lx * lx + ly * ly);

      int wx = (int)(2 * -ly * w / len);
      int wy = (int)(2 *  lx * w / len);

      x[0] = ((ax<<2)-wx)>>2;
      x[1] = ((ax<<2)+wx)>>2;
      x[2] = ((bx<<2)+wx)>>2;
      x[3] = ((bx<<2)-wx)>>2;

      y[0] = ((ay<<2)-wy)>>2;
      y[1] = ((ay<<2)+wy)>>2;
      y[2] = ((by<<2)+wy)>>2;
      y[3] = ((by<<2)-wy)>>2;
   }

///////////// KEN'S HANDY-DANDY LINE FONT

   static public int font[][] = new int[256][];
   static int fontData[][] = {
      { 0 }, {0, 0, 6, 0, 6,10, 0,10, 0, 0,-1},

      {' '}, {NULL},
      {'`'}, {2, 0, 4, 2,NULL},
      {'~'}, {0, 5, 2, 4, 4, 5, 6, 4,NULL},
      {'!'}, {3, 0, 3, 7, 0,NULL, 3, 9, 3,10,NULL},
      {'@'}, {6, 8, 4,10, 2,10, 0, 8, 0, 2, 2, 0, 4, 0, 6, 2, 6, 6, 4, 8, 2, 6, 2, 4, 4, 3, 4, 8,NULL},
      {'#'}, {2, 0, 1,10, 0,NULL, 5, 0, 4,10, 0,NULL, 0, 3, 6, 3, 0,NULL, 0, 7, 6, 7,NULL},
      {'$'}, {5, 2, 4, 1, 2, 1, 1, 2, 1, 4, 2, 5,
              4, 5, 5, 6, 5, 8, 4, 9, 2, 9, 1, 8, 0,NULL,
              3, 0, 3,10,NULL},
      {'%'}, {0,10, 6, 0, 0,NULL, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0,NULL,
              4, 8, 4,10, 6,10, 6, 8, 4, 8,NULL},
      {'^'}, {1, 2, 3, 0, 5, 2,NULL},
      {'&'}, {6,10, 1, 3, 1, 1, 2, 0, 3, 0, 4, 1, 4, 3, 0, 8, 0, 9, 1,10,
              3,10, 6, 7,NULL},
      {'*'}, {3, 0, 3, 3, 0,NULL, 0, 2, 3, 3, 0,NULL, 6, 2, 3, 3, 0, NULL,
              1, 6, 3, 3, 0,NULL, 5, 6, 3, 3,NULL},
      {'('}, {4, 0, 3, 3, 3, 7, 4,10,NULL},
      {')'}, {2, 0, 3, 3, 3, 7, 2,10,NULL},
      {'-'}, {1, 5, 5, 5,NULL},
      {'_'}, {0,11, 6,11,NULL},
      {'='}, {1, 4, 5, 4, 0,NULL, 1, 6, 5, 6,NULL},
      {'+'}, {0, 5, 6, 5, 0,NULL, 3, 2, 3, 8,NULL},
      {'['}, {4, 0, 2, 0, 2,10, 4,10,NULL},
      {'{'}, {4, 0, 3, 1, 3, 4, 2, 5, 3, 6, 3, 9, 4,10,NULL},
      {']'}, {2, 0, 4, 0, 4,10, 2,10,NULL},
      {'}'}, {2, 0, 3, 1, 3, 4, 4, 5, 3, 6, 3, 9, 2,10,NULL},
      {'\\'},{1, 0, 5,10,NULL},
      {'|'}, {3, 0, 3,10,NULL},
      {';'}, {3, 3, 3, 4, 0,NULL, 3, 8, 3, 9, 2,11,NULL},
      {':'}, {3, 3, 3, 4, 0,NULL, 3, 8, 3, 9,NULL},
      {'\''},{4, 0, 2, 2,NULL},
      {'"'}, {2, 0, 2, 2, 0,NULL, 4, 0, 4, 2,NULL},
      {','}, {3, 8, 3, 9, 2,11,NULL},
      {'<'}, {5, 1, 1, 5, 5, 9,NULL},
      {'.'}, {3, 9, 3,10,NULL},
      {'>'}, {1, 1, 5, 5, 1, 9,NULL},
      {'/'}, {5, 0, 1,10,NULL},
      {'?'}, {0, 1, 1, 0, 5, 0, 6, 1, 6, 4, 3, 6, 3, 7, 0,NULL, 3, 9, 3,10,NULL}, 

      {'0'}, {6, 5, 6, 2, 4, 0, 2, 0, 0, 2, 0, 8, 2,10,4,10, 6, 8, 6, 5,NULL},
      {'1'}, {1, 2, 3, 0, 3,10,NULL},
      {'2'}, {0, 1, 1, 0, 5, 0, 6, 1, 6, 4, 0,10, 6,10,NULL},
      {'3'}, {0, 1, 1, 0, 5, 0, 6, 1, 6, 4, 4, 5, 6, 6, 6, 9, 5,10, 1,10, 0, 9,NULL}, 
      {'4'}, {5,10, 5, 0, 0, 5, 6, 5,NULL},
      {'5'}, {6, 0, 0, 0, 0, 5, 5, 5, 6, 6, 6, 9, 5,10, 1,10, 0, 9,NULL},
      {'6'}, {6, 1, 5, 0, 1, 0, 0, 1, 0, 9, 1,10, 5,10, 6, 9, 6, 6, 5, 5, 0, 5,NULL},
      {'7'}, {0, 0, 6, 0, 3,10,NULL},
      {'8'}, {2, 5, 1, 5, 0, 4, 0, 1, 1, 0, 5, 0, 6, 1, 6, 4, 5, 5, 1, 5,
              0, 6, 0, 9, 1,10, 5,10, 6, 9, 6, 6, 5, 5, 3, 5,NULL},
      {'9'}, {0, 9, 1,10, 5,10, 6, 9, 6, 1, 5, 0, 1, 0, 0, 1, 0, 4, 1, 5, 6, 5,NULL},

      {'a'}, {0, 5, 1, 4, 5, 4, 6, 5, 6,10, 0,NULL,
              6, 8, 4,10, 1,10, 0, 9, 0, 8, 1, 7, 6, 7,NULL},
      {'A'}, {0,10, 3, 0, 6,10, 0,NULL, 1, 7, 5, 7,NULL},
      {'b'}, {0, 0, 0,10, 0,NULL,
              0, 6, 2, 4, 5, 4, 6, 5, 6, 9, 5,10, 2,10, 0, 8,NULL},
      {'B'}, {0, 5, 0, 0, 5, 0, 6, 1, 6, 4, 5, 5, 0,NULL,
              0, 5, 0,10, 5,10, 6, 9, 6, 6, 5, 5, 0, 5,NULL},
      {'c'}, {6, 5, 5, 4, 1, 4, 0, 5, 0, 9, 1,10, 5,10, 6, 9,NULL},
      {'C'}, {6, 1, 5, 0, 1, 0, 0, 1, 0, 9, 1,10, 5,10, 6, 9,NULL},
      {'d'}, {6, 0, 6,10, 0,NULL,
              6, 6, 4, 4, 1, 4, 0, 5, 0, 9, 1,10, 4,10, 6, 8,NULL},
      {'D'}, {0, 5, 0, 0, 4, 0, 6, 2, 6, 8, 4,10, 0,10, 0, 5,NULL},
      {'e'}, {0, 7, 6, 7, 6, 5, 5, 4, 1, 4, 0, 5, 0, 9, 1,10, 5,10, 6, 9,NULL},
      {'E'}, {6, 0, 0, 0, 0,10, 6,10, 0,NULL, 0, 5, 4, 5,NULL},
      {'f'}, {2,10, 2, 1, 3, 0, 5, 0, 6, 1, 0,NULL, 0, 4, 6, 4,NULL},
      {'F'}, {6, 0, 0, 0, 0,10, 0,NULL, 0, 5, 4, 5,NULL},
      {'g'}, {0,12, 1,13, 5,13, 6,12, 6, 4, 0,NULL,
              6, 6, 4, 4, 1, 4, 0, 5, 0, 9, 1,10, 4,10, 6, 8,NULL},
      {'G'}, {6, 1, 5, 0, 1, 0, 0, 1, 0, 9, 1,10, 5,10, 6, 9, 6, 5, 3, 5,NULL},
      {'h'}, {0, 0, 0,10, 0,NULL,
              0, 6, 2, 4, 5, 4, 6, 5, 6,10,NULL},
      {'H'}, {0, 0, 0,10, 0,NULL, 6, 0, 6,10, 0,NULL, 0, 5, 6, 5,NULL},
      {'i'}, {3,10, 3, 4, 0,NULL, 3, 1, 3, 0,NULL},
      {'I'}, {3,10, 3, 0, 0,NULL, 1, 0, 5, 0, 0,NULL, 1,10, 5,10,NULL},
      {'j'}, {5, 4, 5,12, 4,13, 1,13, 0,12, 0,NULL, 5, 1, 5, 0,NULL},
      {'J'}, {6, 0, 6, 9, 5,10, 1,10, 0, 9, 0, 8,NULL},
      {'k'}, {0, 0, 0,10, 0,NULL, 6, 4, 1, 7, 6,10,NULL},
      {'K'}, {0, 0, 0,10, 0,NULL, 6, 0, 1, 5, 6,10,NULL},
      {'l'}, {2, 0, 3, 0, 3,10,NULL},
      {'L'}, {0, 0, 0,10, 6,10,NULL},
      {'m'}, {0,10, 0, 4, 0,NULL,
              0, 4, 2, 4, 3, 5, 3,10, 3, 5, 4, 4, 5, 4, 6, 5, 6,10,NULL},
      {'M'}, {0,10, 0, 0, 3, 4, 6, 0, 6,10,NULL},
      {'n'}, {0,10, 0, 4, 0,NULL,
              0, 6, 2, 4, 5, 4, 6, 5, 6,10,NULL},
      {'N'}, {0,10, 0, 0, 6,10, 6, 0,NULL},
      {'o'}, {6, 7, 6, 5, 5, 4, 1, 4, 0, 5, 0, 9, 1,10,5,10, 6, 9, 6, 7,NULL},
      {'O'}, {6, 5, 6, 1, 5, 0, 1, 0, 0, 1, 0, 9, 1,10,5,10, 6, 9, 6, 5,NULL},
      {'p'}, {0, 4, 0,13, 0,NULL,
              0, 6, 2, 4, 5, 4, 6, 5, 6, 9, 5,10, 2,10, 0, 8,NULL},
      {'P'}, {0,10, 0, 0, 5, 0, 6, 1, 6, 4, 5, 5, 0, 5,NULL},
      {'q'}, {6, 4, 6,13, 0,NULL,
              6, 6, 4, 4, 1, 4, 0, 5, 0, 9, 1,10, 4,10, 6, 8,NULL},
      {'Q'}, {6, 5, 6, 1, 5, 0, 1, 0, 0, 1, 0, 9, 1,10,4,10, 6, 8, 6, 5, 0,NULL,
              3, 7, 6,10,NULL},
      {'r'}, {0,10, 0, 4, 0,NULL,
              0, 6, 2, 4, 5, 4, 6, 5,NULL},
      {'R'}, {0,10, 0, 0, 5, 0, 6, 1, 6, 4, 5, 5, 0, 5, 0,NULL, 3, 5, 6,10,NULL},
      {'s'}, {6, 5, 5, 4, 1, 4, 0, 5, 0, 6, 1, 7,
              5, 7, 6, 8, 6, 9, 5,10, 1,10, 0, 9,NULL},
      {'S'}, {6, 1, 5, 0, 1, 0, 0, 1, 0, 4, 1, 5,
              5, 5, 6, 6, 6, 9, 5,10, 1,10, 0, 9,NULL},
      {'t'}, {2, 2, 2, 9, 3,10, 5,10, 6, 9, 0,NULL, 0, 4, 6, 4,NULL},
      {'T'}, {0, 0, 6, 0, 0,NULL, 3, 0, 3,10,NULL},
      {'u'}, {6, 4, 6,10, 0,NULL,
              6, 8, 4,10, 1,10, 0, 9, 0, 4,NULL},
      {'U'}, {0, 0, 0, 9, 1,10, 5,10, 6, 9, 6, 0,NULL},
      {'v'}, {0, 4, 3,10, 6, 4,NULL},
      {'V'}, {0, 0, 3,10, 6, 0,NULL},
      {'w'}, {0, 4, 1,10, 3, 7, 5,10, 6, 4,NULL},
      {'W'}, {0, 0, 1,10, 3, 5, 5,10, 6, 0,NULL},
      {'x'}, {0, 4, 6,10, 0,NULL, 0,10, 6, 4,NULL},
      {'X'}, {0, 0, 6,10, 0,NULL, 0,10, 6, 0,NULL},
      {'y'}, {0, 4, 3,10, 0,NULL, 6, 4, 2,13,NULL},
      {'Y'}, {0, 0, 3, 5, 0,NULL, 6, 0, 3, 5, 0,NULL, 3, 5, 3,10,NULL},
      {'z'}, {0, 4, 6, 4, 0,10, 6,10,NULL},
      {'Z'}, {0, 0, 6, 0, 0,10, 6,10,NULL},
   };
   static boolean initFont = initFont();
   static boolean initFont() {
      for (int i = 0 ; i < fontData.length ; i += 2)
         font[fontData[i][0]] = fontData[i+1];
      return true;
   }
}


