/*******************************************************************************
*                                                                              *
*  File:        PixelGraphics.js                        Revision:  1.0         *
*                                                                              *
*  Purpose:     provides support for simple pixel graphics under JavaScript    *
*                                                                              *
*  Creation:    20.06.1998                     Last Modification:  03.05.2002  *
*                                                                              *
*  Platform:    IBM-compatible PC running Windows 98SE                         *
*                                                                              *
*  Environment: JavaScript 1.3                                                 *
*                                                                              *
*  Author:      Andreas Rozek           Phone:  ++49 (711) 6770682             *
*               Kirschblütenweg 15      Fax:    -                              *
*             D-70569 Stuttgart         EMail:  Andreas.Rozek@T-Online.De      *
*               Germany                                                        *
*                                                                              *
*  URL:         http://www.Andreas-Rozek.de/                                   *
*                                                                              *
*  Copyright:   this software is published under the  "GNU Lesser General Pub- *
*               lic  License"  (see  "http://www.fsf.org/copyleft/lesser.html" *
*               for additional information)                                    *
*                                                                              *
*  Comments:    (none)                                                         *
*                                                                              *
*******************************************************************************/

  var _Prefix    = "Display";                        // defines the display name
  var _PixelSize = 4;                // defines the "natural" size of each pixel

/**** table of supported colors (each color requires a corresponding pixel image) ****/

  function newImage (ImageURL) {
    var Result = new Image(_PixelSize,_PixelSize);
      Result.src = ImageURL;
    return Result;
  };

  var Black   = newImage("./BlackPixel.gif");
  var Gray    = newImage("./GrayPixel.gif");
  var Maroon  = newImage("./MaroonPixel.gif");
  var Red     = newImage("./RedPixel.gif");
  var Green   = newImage("./GreenPixel.gif");
  var Lime    = newImage("./LimePixel.gif");
  var Olive   = newImage("./OlivePixel.gif");
  var Yellow  = newImage("./YellowPixel.gif");
  var Navy    = newImage("./NavyPixel.gif");
  var Blue    = newImage("./BluePixel.gif");
  var Purple  = newImage("./PurplePixel.gif");
  var Fuchsia = newImage("./FuchsiaPixel.gif");
  var Teal    = newImage("./TealPixel.gif");
  var Aqua    = newImage("./AquaPixel.gif");
  var Silver  = newImage("./SilverPixel.gif");
  var White   = newImage("./WhitePixel.gif");

  var _ColorTable = new Object();
    _ColorTable["black"]   = Black;   _ColorTable[0]  = Black;
    _ColorTable["gray"]    = Gray;    _ColorTable[1]  = Gray;
    _ColorTable["maroon"]  = Maroon;  _ColorTable[2]  = Maroon;
    _ColorTable["red"]     = Red;     _ColorTable[3]  = Red;
    _ColorTable["green"]   = Green;   _ColorTable[4]  = Green;
    _ColorTable["lime"]    = Lime;    _ColorTable[5]  = Lime;
    _ColorTable["olive"]   = Olive;   _ColorTable[6]  = Olive;
    _ColorTable["yellow"]  = Yellow;  _ColorTable[7]  = Yellow;
    _ColorTable["navy"]    = Navy;    _ColorTable[8]  = Navy;
    _ColorTable["blue"]    = Blue;    _ColorTable[9]  = Blue;
    _ColorTable["purple"]  = Purple;  _ColorTable[10] = Purple;
    _ColorTable["fuchsia"] = Fuchsia; _ColorTable[11] = Fuchsia;
    _ColorTable["teal"]    = Teal;    _ColorTable[12] = Teal;
    _ColorTable["aqua"]    = Aqua;    _ColorTable[13] = Aqua;
    _ColorTable["silver"]  = Silver;  _ColorTable[14] = Silver;
    _ColorTable["white"]   = White;   _ColorTable[15] = White;

/**** context settings ****/

  var _DisplayWidth  = 0;                   // width of graphics display [pixel]
  var _DisplayHeight = 0;                  // height of graphics display [pixel]

  var _PenColor  = Black;                        // pixel and line drawing color
  var _FillColor = Black;                                     // area fill color

/*******************************************************************************
*                                                                              *
* _ClipCode      determines the Cohen/Sutherland "clip code" for a given point *
*                                                                              *
*******************************************************************************/

  var _Left = 1;  var _Right = 2;  var _Bottom = 4;  var _Top = 8;
  function _ClipCode (x,y) {
    return (x < 0 ? _Left : (x >= _DisplayWidth  ? _Right  : 0)) +
           (y < 0 ? _Top  : (y >= _DisplayHeight ? _Bottom : 0));
  };

/*******************************************************************************
*                                                                              *
* _setPixel                              sets the given pixel to a given color *
*                                                                              *
*******************************************************************************/

  function _setPixel (x,y, Color) {                      // no parameter checks!
    try {
      x++; y++;                  // really weird: JavaScript may produce "-0"!!!
      document.images[_Prefix+x+"_"+y].src = Color.src;
    } catch (Exception) {
      document.writeln("manipulation of Pixel ",x,",",y," failed!<br>");
    };
  };

/*******************************************************************************
*                                                                              *
* assert                            checks the fulfilment of a given condition *
*                                                                              *
*******************************************************************************/

  function assert (Condition, Message) {
    if (Condition == false) {
      if (Message == null) {
        document.writeln("Assertion failed!<br>");
      } else {
        document.write  (Message);
        document.writeln("<br>");
      };
    };
  };

/*******************************************************************************
*                                                                              *
* clearDisplay                               clears the whole graphics display *
*                                                                              *
*******************************************************************************/

  function clearDisplay () {
    var Color        = White.src;
    var PixelPattern = _Prefix+"[_]";

    for (var y = 1; y <= _DisplayHeight; y++) {
      var RowPattern = PixelPattern.replace(/\]/,y);

      for (var x = 1; x <= _DisplayWidth; x++) {
        document.images[RowPattern.replace(/\[/,x)].src = Color;
      };
    };
  };

/*******************************************************************************
*                                                                              *
* createDisplay                          creates a table for the pixel display *
*                                                                              *
*******************************************************************************/

  function createDisplay (Width, Height, Color) {
    if (Width  == null) Width  = 160;      // handles "undefined" values as well
    if (Height == null) Height = 120;                                    // dto.
    if (Color  == null) Color  = White;                                  // dto.

    _DisplayWidth  = max(2,min(320,Width));                // do not exaggerate!
    _DisplayHeight = max(2,min(240,Height));                             // dto.

    if (typeof(Color) == "string") Color = _ColorTable[Color];
    if (Color == null) Color = White;

  /**** construct cell template ****/

    var CellTemplate =
      "<img name=\"" + _Prefix + "[_]\" src=\"" + Color.src + "\" " +
      "width=" + _PixelSize + " height=" + _PixelSize + ">";

  /**** construct row template ****/

    var RowTemplate = "  <tr><td nowrap height=" + _PixelSize + ">";
      for (var Col = 1; Col <= _DisplayWidth; Col++) {
        RowTemplate += CellTemplate.replace(/\[/,Col);
      };
    RowTemplate += "<\/td><\/tr>";

  /**** construct display table ****/

    document.writeln("<table border=0 cellspacing=0 cellpadding=0 width=" + (_DisplayWidth*_PixelSize) + ">");
    document.writeln("  <colgroup span=1><\/colgroup>");
      for (var Row = 1; Row <= _DisplayHeight; Row++) {
        document.writeln(RowTemplate.replace(/\]/g,Row));
      };
    document.writeln("<\/table>");
  };

/*******************************************************************************
*                                                                              *
* drawArc                                           draws a "regular" oval arc *
*                                                                              *
*******************************************************************************/

  function drawArc (X1,Y1, X2,Y2, StartAngle,StopAngle) {
    var TwoPi = 2*Math.PI;

    var DeltaAngle = StopAngle-StartAngle;  // checks if the "arc" forms an oval
    if (Math.abs(DeltaAngle) >= TwoPi) {
      drawOval(X1,Y1, X2,Y2);        // prefer "drawOval" to draw complete ovals
      return;
    };

    X1 = Math.round(X1);  X2 = Math.round(X2);  if (X1 > X2) {var X = X1; X1 = X2; X2 = X;};
    Y1 = Math.round(Y1);  Y2 = Math.round(Y2);  if (Y2 > Y1) {var Y = Y1; Y1 = Y2; Y2 = Y;};

    if ((X1 >= _DisplayWidth) || (X2 < 0) || (Y1 >= _DisplayHeight) || (Y2 < 0)) return;

    var Reduction = StartAngle - (StartAngle % TwoPi);
    if (Reduction != 0) {
      StartAngle -= Reduction;
      StopAngle  -= Reduction;
    };

    var A = (X2-X1)/2;  var XM = X1+A;
    var B = (Y2-Y1)/2;  var YM = Y1+B;

    if ((A == 0) && (B == 0)) {
      drawPixel(X1,Y1);                // draw a pixel instead of a degraded arc
      return;
    };

    var SinPhi = 1/Math.sqrt(A*A+B*B);
    var CosPhi = Math.sqrt(1 - SinPhi*SinPhi);

    var Count = (StopAngle-StartAngle) / SinPhi;                      // tricky!
    if (Count < 0) {Count = -Count; SinPhi = -SinPhi};       // rotate clockwise
    Count = Math.ceil(Count);

    var X = Math.cos(StartAngle);  var XNew = 0;
    var Y = Math.sin(StartAngle);  var YNew = 0;
    for (var i = 0; i < Count; i++) {
      drawPixel(XM+A*X,YM+B*Y);

      XNew = X*CosPhi - Y*SinPhi;
      YNew = X*SinPhi + Y*CosPhi;

      X = XNew;
      Y = YNew;
    };
  };

/*******************************************************************************
*                                                                              *
* drawChord                                       draws a "regular" oval chord *
*                                                                              *
*******************************************************************************/

  function drawChord (X1,Y1, X2,Y2, StartAngle,StopAngle) {
    var TwoPi = 2*Math.PI;

    var DeltaAngle = StopAngle-StartAngle;  // checks if the "arc" forms an oval
    if (Math.abs(DeltaAngle) >= TwoPi) {
      drawOval(X1,Y1, X2,Y2);        // prefer "drawOval" to draw complete ovals
      return;
    };

    X1 = Math.round(X1);  X2 = Math.round(X2);  if (X1 > X2) {var X = X1; X1 = X2; X2 = X;};
    Y1 = Math.round(Y1);  Y2 = Math.round(Y2);  if (Y2 > Y1) {var Y = Y1; Y1 = Y2; Y2 = Y;};

    if ((X1 >= _DisplayWidth) || (X2 < 0) || (Y1 >= _DisplayHeight) || (Y2 < 0)) return;

    var Reduction = StartAngle - (StartAngle % TwoPi);
    if (Reduction != 0) {
      StartAngle -= Reduction;
      StopAngle  -= Reduction;
    };

    var A = (X2-X1)/2;  var XM = X1+A;
    var B = (Y2-Y1)/2;  var YM = Y1+B;

    if ((A == 0) && (B == 0)) {
      drawPixel(X1,Y1);                // draw a pixel instead of a degraded arc
      return;
    };

    var SinPhi = 1/Math.sqrt(A*A+B*B);
    var CosPhi = Math.sqrt(1 - SinPhi*SinPhi);

    var Count = (StopAngle-StartAngle) / SinPhi;                      // tricky!
    if (Count < 0) {Count = -Count; SinPhi = -SinPhi};       // rotate clockwise
    Count = Math.ceil(Count);

    var X = Math.cos(StartAngle);  var XNew = 0;
    var Y = Math.sin(StartAngle);  var YNew = 0;
    for (var i = 0; i < Count; i++) {
      drawPixel(XM+A*X,YM+B*Y);

      XNew = X*CosPhi - Y*SinPhi;
      YNew = X*SinPhi + Y*CosPhi;

      X = XNew;
      Y = YNew;
    };

    drawLine(
      XM+A*Math.cos(StartAngle),YM+B*Math.sin(StartAngle),
      XM+A*Math.cos(StopAngle), YM+B*Math.sin(StopAngle)
    );
  };

/*******************************************************************************
*                                                                              *
* drawImage                                                draws a given image *
*                                                                              *
*******************************************************************************/

  function drawImage (Bitmap, X,Y) {
    X = Math.round(X);  var Width  = Bitmap[0].length-1;
    Y = Math.round(Y);  var Height = Bitmap.length-1;

  /**** check visibility ****/

    if ((Width < 0) || (Height < 0)) return;

    if ((X >= _DisplayWidth)  || (X+Width  < 0)) return;
    if ((Y >= _DisplayHeight) || (Y+Height < 0)) return;

  /**** perform clipping ****/

    var XL = max(0,min(X,_DisplayWidth-1));   var XR = max(0,min(X+Width, _DisplayWidth-1));
    var YT = max(0,min(Y,_DisplayHeight-1));  var YB = max(0,min(Y+Height,_DisplayHeight-1));

  /**** now draw the image ****/

    var Row, Col, X1, Y1, PixelColor;
    for (Y1 = YT, Row = YT-Y; Y1 <= YB; Y1++, Row++) {
      for (X1 = XL, Col = XL-X; X1 <= XR; X1++, Col++) {
        PixelColor = _ColorTable[Bitmap[Col][Row]];
        if (PixelColor != null) _setPixel(X1,Y1,PixelColor);
      };
    };
  };

/*******************************************************************************
*                                                                              *
* drawLine                                                 draws a single line *
*                                                                              *
*******************************************************************************/

  function drawLine (X1,Y1, X2,Y2) {
    X1 = Math.round(X1);  X2 = Math.round(X2);
    Y1 = Math.round(Y1);  Y2 = Math.round(Y2);

  /**** handle vertical lines separately ****/

    if (X1 == X2) {
      if ((X1 < 0) || (X1 >= _DisplayWidth)) return;  // line outside of display

      if (Y2 < Y1) {var Y = Y1; Y1 = Y2; Y2 = Y;};       // unify loop direction

      Y1 = max(0,min(Y1,_DisplayHeight-1));                    // apply clipping
      Y2 = max(0,min(Y2,_DisplayHeight-1));

      for (var Y = Y1; Y <= Y2; Y++) {                     // draw vertical line
        _setPixel(X1,Y,_PenColor);
      };

      return;
    };

  /**** handle horizontal lines separately ****/

    if (Y1 == Y2) {
      if ((Y1 < 0) || (Y1 >= _DisplayHeight)) return; // line outside of display

      if (X2 < X1) {var X = X1; X1 = X2; X2 = X;};       // unify loop direction

      X1 = max(0,min(X1,_DisplayWidth-1));                     // apply clipping
      X2 = max(0,min(X2,_DisplayWidth-1));

      for (var X = X1; X <= X2; X++) {                   // draw horizontal line
        _setPixel(X,Y1,_PenColor);
      };

      return;
    };

  /**** check visibility of sloped lines, if need be: apply clipping ****/

    var ClipCode1 = _ClipCode(X1,Y1);
    var ClipCode2 = _ClipCode(X2,Y2);

    var X,Y;
    while ((ClipCode1 != 0) || (ClipCode2 != 0)) {// endpoint(s) outside display
      if ((ClipCode1 & ClipCode2) != 0) return;    // line is entirely invisible

      var ClipCode = (ClipCode1 != 0 ? ClipCode1 : ClipCode2);
      if        ((ClipCode & _Left) != 0) {            // line crosses left edge
        Y = Math.round(Y1 - (Y2-Y1)*X1/(X2-X1));
        X = 0;
      } else if ((ClipCode & _Right) != 0) {          // line crosses right edge
        Y = Math.round(Y1 + (Y2-Y1)*(_DisplayWidth-1-X1)/(X2-X1));
        X = _DisplayWidth-1;
      } else if ((ClipCode & _Bottom) != 0) {         // line crosses lower edge
        X = Math.round(X1 + (X2-X1)*(_DisplayHeight-1-Y1)/(Y2-Y1));
        Y = _DisplayHeight-1;
      } else if ((ClipCode & _Top) != 0) {            // line crosses upper edge
        X = Math.round(X1 - (X2-X1)*Y1/(Y2-Y1));
        Y = 0;
      };

      if (ClipCode == ClipCode1) {
        X1 = X; Y1 = Y; ClipCode1 = _ClipCode(X1,Y1);
      } else {
        X2 = X; Y2 = Y; ClipCode2 = _ClipCode(X2,Y2);
      };
    };

  /**** handle visible part of sloped lines (Bresenham) ****/

    var DeltaX = Math.abs(X1-X2);
    var DeltaY = Math.abs(Y1-Y2);

    if (DeltaX >= DeltaY) {
      if (X2 < X1) {                                     // unify loop direction
        var X = X1; X1 = X2; X2 = X;
        var Y = Y1; Y1 = Y2; Y2 = Y;
      };

      var Error  = 2*DeltaY-DeltaX;
      var Delta1 = 2*(DeltaY-DeltaX);
      var Delta2 = 2*DeltaY;

      var Offset = (Y2 > Y1 ? +1 : -1);

      for (var i = 0; i <= DeltaX; i++) {
        _setPixel(X1,Y1,_PenColor);

        if (Error > 0) {
          Y1 += Offset;
          Error += Delta1;                                  // 2*(DeltaY-DeltaX)
        } else {
          Error += Delta2;                                           // 2*DeltaY
        };

        X1++;
      };
    } else {
      if (Y2 < Y1) {                                     // unify loop direction
        var X = X1; X1 = X2; X2 = X;
        var Y = Y1; Y1 = Y2; Y2 = Y;
      };

      var Error  = 2*DeltaX-DeltaY;
      var Delta1 = 2*(DeltaX-DeltaY);
      var Delta2 = 2*DeltaX;

      var Offset = (X2 > X1 ? +1 : -1);

      for (var i = 0; i <= DeltaY; i++) {
        _setPixel(X1,Y1,_PenColor);

        if (Error > 0) {
          X1 += Offset;
          Error += Delta1;                                  // 2*(DeltaX-DeltaY)
        } else {
          Error += Delta2;                                           // 2*DeltaX
        };

        Y1++;
      };
    };
  };

/*******************************************************************************
*                                                                              *
* drawOval                                              draws a "regular" oval *
*                                                                              *
*******************************************************************************/

  function drawOval (X1,Y1, X2,Y2) {
    X1 = Math.round(X1);  X2 = Math.round(X2);  if (X1 > X2) {var X = X1; X1 = X2; X2 = X;};
    Y1 = Math.round(Y1);  Y2 = Math.round(Y2);  if (Y2 > Y1) {var Y = Y1; Y1 = Y2; Y2 = Y;};

    if ((X1 >= _DisplayWidth) || (X2 < 0) || (Y1 >= _DisplayHeight) || (Y2 < 0)) return;

    var A = (X2-X1)/2;  var XM = X1+A;
    var B = (Y2-Y1)/2;  var YM = Y1+B;

    if ((A == 0) || (B == 0)) {
      drawLine(X1,Y1, X2,Y2);          // draw a line instead of a degraded oval
      return;
    };

    var SinPhi = 1/Math.sqrt(A*A+B*B);
    var CosPhi = Math.sqrt(1 - SinPhi*SinPhi);

    var X = 1; var Y = 0;  var XNew = 0; var YNew = 0;
    while (X > -SinPhi) {
      drawPixel(XM+A*X,YM+B*Y);  drawPixel(XM-A*X,YM+B*Y);
      drawPixel(XM+A*X,YM-B*Y);  drawPixel(XM-A*X,YM-B*Y);

      XNew = X*CosPhi - Y*SinPhi;
      YNew = X*SinPhi + Y*CosPhi;

      X = XNew;
      Y = YNew;
    };
  };

/*******************************************************************************
*                                                                              *
* drawPie                                           draws a "regular" oval pie *
*                                                                              *
*******************************************************************************/

  function drawPie (X1,Y1, X2,Y2, StartAngle,StopAngle) {
    var TwoPi = 2*Math.PI;

    var DeltaAngle = StopAngle-StartAngle;  // checks if the "arc" forms an oval
    if (Math.abs(DeltaAngle) >= TwoPi) {
      drawOval(X1,Y1, X2,Y2);        // prefer "drawOval" to draw complete ovals
      return;
    };

    X1 = Math.round(X1);  X2 = Math.round(X2);  if (X1 > X2) {var X = X1; X1 = X2; X2 = X;};
    Y1 = Math.round(Y1);  Y2 = Math.round(Y2);  if (Y2 > Y1) {var Y = Y1; Y1 = Y2; Y2 = Y;};

    if ((X1 >= _DisplayWidth) || (X2 < 0) || (Y1 >= _DisplayHeight) || (Y2 < 0)) return;

    var Reduction = StartAngle - (StartAngle % TwoPi);
    if (Reduction != 0) {
      StartAngle -= Reduction;
      StopAngle  -= Reduction;
    };

    var A = (X2-X1)/2;  var XM = X1+A;
    var B = (Y2-Y1)/2;  var YM = Y1+B;

    if ((A == 0) && (B == 0)) {
      drawPixel(X1,Y1);                // draw a pixel instead of a degraded arc
      return;
    };

    var SinPhi = 1/Math.sqrt(A*A+B*B);
    var CosPhi = Math.sqrt(1 - SinPhi*SinPhi);

    var Count = (StopAngle-StartAngle) / SinPhi;                      // tricky!
    if (Count < 0) {Count = -Count; SinPhi = -SinPhi};       // rotate clockwise
    Count = Math.ceil(Count);

    var X = Math.cos(StartAngle);  var XNew = 0;
    var Y = Math.sin(StartAngle);  var YNew = 0;

    drawLine(XM,YM, XM+A*Math.cos(StartAngle),YM+B*Math.sin(StartAngle));
      for (var i = 0; i < Count; i++) {
        drawPixel(XM+A*X,YM+B*Y);

        XNew = X*CosPhi - Y*SinPhi;
        YNew = X*SinPhi + Y*CosPhi;

        X = XNew;
        Y = YNew;
      };
    drawLine(XM+A*Math.cos(StopAngle),YM+B*Math.sin(StopAngle), XM,YM);
  };

/*******************************************************************************
*                                                                              *
* drawPixel                                               draws a single pixel *
*                                                                              *
*******************************************************************************/

  function drawPixel (X,Y) {
    X = Math.round(X);
    Y = Math.round(Y);

    if ((X >= 0) && (X < _DisplayWidth) && (Y >= 0) && (Y < _DisplayHeight)) {
      _setPixel(X,Y,_PenColor);
    };
  };

/*******************************************************************************
*                                                                              *
* drawPolygon                                         draws a (closed) polygon *
*                                                                              *
*******************************************************************************/

  function drawPolygon (NodeX,NodeY,NodeCount) {
    if (typeof(NodeY) == "number") {
      var NodeCount = min(NodeX.length/2,NodeY)-1;
      for (var i = 0; i < 2*NodeCount; i+=2) {
        drawLine(NodeX[i],NodeX[i+1], NodeX[i+2],NodeX[i+3]);
      };

      if ((NodeX[0] != NodeX[NodeX.length-2]) && (NodeX[1] != NodeX[NodeX.length-1])) {
        drawLine(NodeX[NodeX.length-2],NodeX[NodeX.length-1], NodeX[0],NodeX[1]);
      };
    } else {
      NodeCount--;
      for (var i = 0; i < NodeCount; i++) {
        drawLine(NodeX[i],NodeY[i], NodeX[i+1],NodeY[i+1]);
      };

      if ((NodeX[0] != NodeX[NodeX.length-1]) && (NodeY[0] != NodeY[NodeX.length-1])) {
        drawLine(NodeX[NodeX.length-1],NodeY[NodeX.length-1], NodeX[0],NodeY[0]);
      };
    };
  };

/*******************************************************************************
*                                                                              *
* drawPolyline                                   draws an (open) line sequence *
*                                                                              *
*******************************************************************************/

  function drawPolyline (NodeX,NodeY,NodeCount) {
    if (typeof(NodeY) == "number") {
      var NodeCount = min(NodeX.length/2,NodeY)-1;
      for (var i = 0; i < 2*NodeCount; i+=2) {
        drawLine(NodeX[i],NodeX[i+1], NodeX[i+2],NodeX[i+3]);
      };
    } else {
      NodeCount--;
      for (var i = 0; i < NodeCount; i++) {
        drawLine(NodeX[i],NodeY[i], NodeX[i+1],NodeY[i+1]);
      };
    };
  };

/*******************************************************************************
*                                                                              *
* drawRectangle                                    draws a "regular" rectangle *
*                                                                              *
*******************************************************************************/

  function drawRectangle (X1,Y1, X2,Y2) {
    X1 = Math.round(X1);  X2 = Math.round(X2);  if (X2 < X1) {var X = X1; X1 = X2; X2 = X};
    Y1 = Math.round(Y1);  Y2 = Math.round(Y2);  if (Y2 < Y1) {var Y = Y1; Y1 = Y2; Y2 = Y};

    var XL = max(0,min(X1,_DisplayWidth-1));   var XR = max(0,min(X2,_DisplayWidth-1));
    var YT = max(0,min(Y1,_DisplayHeight-1));  var YB = max(0,min(Y2,_DisplayHeight-1));

  /**** draw upper line ****/

    if ((Y1 >= 0) && (Y1 < _DisplayHeight)) {
      for (var X = XL; X <= XR; X++) {
        _setPixel(X,Y1,_PenColor);
      };
    };

  /**** draw left line ****/

    if ((X1 >= 0) && (X1 < _DisplayWidth)) {
      for (var Y = YT+1; Y <= YB; Y++) {
        _setPixel(X1,Y,_PenColor);
      };
    };

  /**** draw right line ****/

    if ((X2 != X1) && (X2 >= 0) && (X2 < _DisplayWidth)) {
      for (var Y = YT+1; Y <= YB; Y++) {
        _setPixel(X2,Y,_PenColor);
      };
    };

  /**** draw lower line ****/

    if ((Y2 != Y1) && (Y2 >= 0) && (Y2 < _DisplayHeight)) {
      for (var X = XL+1; X < XR; X++) {
        _setPixel(X,Y2,_PenColor);
      };
    };
  };

/*******************************************************************************
*                                                                              *
* drawText                                                draws a given string *
*                                                                              *
*******************************************************************************/

  function drawText (String, X,Y) {
//  not yet implemented
  };

/*******************************************************************************
*                                                                              *
* fillChord                                draws a filled "regular" oval chord *
*                                                                              *
*******************************************************************************/

  function fillChord (X1,Y1, X2,Y2, StartAngle,StopAngle) {
//  not yet implemented
  };

/*******************************************************************************
*                                                                              *
* fillOval                                       draws a filled "regular" oval *
*                                                                              *
*******************************************************************************/

  function fillOval (X1,Y1, X2,Y2) {
//  not yet implemented
  };

/*******************************************************************************
*                                                                              *
* fillPie                                    draws a filled "regular" oval pie *
*                                                                              *
*******************************************************************************/

  function fillPie () {
//  not yet implemented
  };

/*******************************************************************************
*                                                                              *
* fillPolygon                                  draws a filled (closed) polygon *
*                                                                              *
*******************************************************************************/

  function fillPolygon (NodeX,NodeY,NodeCount) {
//  not yet implemented
  };

/*******************************************************************************
*                                                                              *
* fillRectangle                             draws a filled "regular" rectangle *
*                                                                              *
*******************************************************************************/

  function fillRectangle (X1,Y1, X2,Y2) {
    X1 = Math.round(X1);  X2 = Math.round(X2);  if (X2 < X1) {var X = X1; X1 = X2; X2 = X};
    Y1 = Math.round(Y1);  Y2 = Math.round(Y2);  if (Y2 < Y1) {var Y = Y1; Y1 = Y2; Y2 = Y};

    var XL = max(0,min(X1,_DisplayWidth-1));   var XR = max(0,min(X2,_DisplayWidth-1));
    var YT = max(0,min(Y1,_DisplayHeight-1));  var YB = max(0,min(Y2,_DisplayHeight-1));

  /**** draw filled rectangle ****/

    for (var Y = YT; Y <= YB; Y++) {
      for (var X = XL; X <= XR; X++) {
        _setPixel(X,Y,_FillColor);
      };
    };
  };

/*******************************************************************************
*                                                                              *
* getDisplayHeight                   yields the height of the graphics display *
*                                                                              *
*******************************************************************************/

  function getDisplayHeight () {
    return _DisplayHeight;
  };

/*******************************************************************************
*                                                                              *
* getDisplayWidth                     yields the width of the graphics display *
*                                                                              *
*******************************************************************************/

  function getDisplayWidth () {
    return _DisplayWidth;
  };

/*******************************************************************************
*                                                                              *
* max/min                         yield the larger/smaller of two given values *
*                                                                              *
*******************************************************************************/

  function max (a,b) {
    return a > b ? a : b;
  };

  function min (a,b) {
    return a < b ? a : b;
  };

/*******************************************************************************
*                                                                              *
* setFillColor                                        sets a new filling color *
*                                                                              *
*******************************************************************************/

  function setFillColor (Color) {
    if        (Color == null) {            // handles "undefined" values as well
      _FillColor = Black;
    } else if (typeof(Color) == "number") {
      _FillColor = _ColorTable[Color];
      if (_FillColor == null) _FillColor = Black;
    } else if (typeof(Color) == "string") {
      _FillColor = _ColorTable[Color.toLowerCase()];
      if (_FillColor == null) _FillColor = Black;
    } else if ((_ColorTable[0]  == Color) || (_ColorTable[1]  == Color) ||
               (_ColorTable[2]  == Color) || (_ColorTable[3]  == Color) ||
               (_ColorTable[4]  == Color) || (_ColorTable[5]  == Color) ||
               (_ColorTable[6]  == Color) || (_ColorTable[7]  == Color) ||
               (_ColorTable[8]  == Color) || (_ColorTable[9]  == Color) ||
               (_ColorTable[10] == Color) || (_ColorTable[11] == Color) ||
               (_ColorTable[12] == Color) || (_ColorTable[13] == Color) ||
               (_ColorTable[14] == Color) || (_ColorTable[15] == Color)) {
      _FillColor = Color;
    } else {
      _FillColor = Black;
    };
  };

/*******************************************************************************
*                                                                              *
* setPenColor                                         sets a new drawing color *
*                                                                              *
*******************************************************************************/

  function setPenColor (Color) {
    if        (Color == null) {            // handles "undefined" values as well
      _PenColor = Black;
    } else if (typeof(Color) == "number") {
      _PenColor = _ColorTable[Color];
      if (_PenColor == null) _PenColor = Black;
    } else if (typeof(Color) == "string") {
      _PenColor = _ColorTable[Color.toLowerCase()];
      if (_PenColor == null) _PenColor = Black;
    } else if ((_ColorTable[0]  == Color) || (_ColorTable[1]  == Color) ||
               (_ColorTable[2]  == Color) || (_ColorTable[3]  == Color) ||
               (_ColorTable[4]  == Color) || (_ColorTable[5]  == Color) ||
               (_ColorTable[6]  == Color) || (_ColorTable[7]  == Color) ||
               (_ColorTable[8]  == Color) || (_ColorTable[9]  == Color) ||
               (_ColorTable[10] == Color) || (_ColorTable[11] == Color) ||
               (_ColorTable[12] == Color) || (_ColorTable[13] == Color) ||
               (_ColorTable[14] == Color) || (_ColorTable[15] == Color)) {
      _PenColor = Color;
    } else {
      _PenColor = Black;
    };
  };
