text: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  
  var Guacamole = Guacamole || {};
  
  /**
   * The Guacamole display. The display does not deal with the Guacamole
   * protocol, and instead implements a set of graphical operations which
   * embody the set of operations present in the protocol. The order operations
   * are executed is guaranteed to be in the same order as their corresponding
   * functions are called.
   * 
   * @constructor
   */
  Guacamole.Display = function() {
  
      /**
       * Reference to this Guacamole.Display.
       * @private
       */
      var guac_display = this;
  
      var displayWidth = 0;
      var displayHeight = 0;
      var displayScale = 1;
  
      // Create display
      var display = document.createElement("div");
      display.style.position = "relative";
      display.style.width = displayWidth + "px";
      display.style.height = displayHeight + "px";
  
      // Ensure transformations on display originate at 0,0
      display.style.transformOrigin =
      display.style.webkitTransformOrigin =
      display.style.MozTransformOrigin =
      display.style.OTransformOrigin =
      display.style.msTransformOrigin =
          "0 0";
  
      // Create default layer
      var default_layer = new Guacamole.Display.VisibleLayer(displayWidth, displayHeight);
  
      // Create cursor layer
      var cursor = new Guacamole.Display.VisibleLayer(0, 0);
      cursor.setChannelMask(Guacamole.Layer.SRC);
  
      // Add default layer and cursor to display
      display.appendChild(default_layer.getElement());
      display.appendChild(cursor.getElement());
  
      // Create bounding div 
      var bounds = document.createElement("div");
      bounds.style.position = "relative";
      bounds.style.width = (displayWidth*displayScale) + "px";
      bounds.style.height = (displayHeight*displayScale) + "px";
  
      // Add display to bounds
      bounds.appendChild(display);
  
      /**
       * The X coordinate of the hotspot of the mouse cursor. The hotspot is
       * the relative location within the image of the mouse cursor at which
       * each click occurs.
       * 
       * @type {Number}
       */
      this.cursorHotspotX = 0;
  
      /**
       * The Y coordinate of the hotspot of the mouse cursor. The hotspot is
       * the relative location within the image of the mouse cursor at which
       * each click occurs.
       * 
       * @type {Number}
       */
      this.cursorHotspotY = 0;
  
      /**
       * The current X coordinate of the local mouse cursor. This is not
       * necessarily the location of the actual mouse - it refers only to
       * the location of the cursor image within the Guacamole display, as
       * last set by moveCursor().
       * 
       * @type {Number}
       */
      this.cursorX = 0;
  
      /**
       * The current X coordinate of the local mouse cursor. This is not
       * necessarily the location of the actual mouse - it refers only to
       * the location of the cursor image within the Guacamole display, as
       * last set by moveCursor().
       * 
       * @type {Number}
       */
      this.cursorY = 0;
  
      /**
       * Fired when the default layer (and thus the entire Guacamole display)
       * is resized.
       * 
       * @event
       * @param {Number} width The new width of the Guacamole display.
       * @param {Number} height The new height of the Guacamole display.
       */
      this.onresize = null;
  
      /**
       * Fired whenever the local cursor image is changed. This can be used to
       * implement special handling of the client-side cursor, or to override
       * the default use of a software cursor layer.
       * 
       * @event
       * @param {HTMLCanvasElement} canvas The cursor image.
       * @param {Number} x The X-coordinate of the cursor hotspot.
       * @param {Number} y The Y-coordinate of the cursor hotspot.
       */
      this.oncursor = null;
  
      /**
       * The queue of all pending Tasks. Tasks will be run in order, with new
       * tasks added at the end of the queue and old tasks removed from the
       * front of the queue (FIFO). These tasks will eventually be grouped
       * into a Frame.
       * @private
       * @type {Task[]}
       */
      var tasks = [];
  
      /**
       * The queue of all frames. Each frame is a pairing of an array of tasks
       * and a callback which must be called when the frame is rendered.
       * @private
       * @type {Frame[]}
       */
      var frames = [];
  
      /**
       * Flushes all pending frames.
       * @private
       */
      function __flush_frames() {
  
          var rendered_frames = 0;
  
          // Draw all pending frames, if ready
          while (rendered_frames < frames.length) {
  
              var frame = frames[rendered_frames];
              if (!frame.isReady())
                  break;
  
              frame.flush();
              rendered_frames++;
  
          } 
  
          // Remove rendered frames from array
          frames.splice(0, rendered_frames);
  
      }
  
      /**
       * An ordered list of tasks which must be executed atomically. Once
       * executed, an associated (and optional) callback will be called.
       *
       * @private
       * @constructor
       * @param {function} callback The function to call when this frame is
       *                            rendered.
       * @param {Task[]} tasks The set of tasks which must be executed to render
       *                       this frame.
       */
      function Frame(callback, tasks) {
  
          /**
           * Returns whether this frame is ready to be rendered. This function
           * returns true if and only if ALL underlying tasks are unblocked.
           * 
           * @returns {Boolean} true if all underlying tasks are unblocked,
           *                    false otherwise.
           */
          this.isReady = function() {
  
              // Search for blocked tasks
              for (var i=0; i < tasks.length; i++) {
                  if (tasks[i].blocked)
                      return false;
              }
  
              // If no blocked tasks, the frame is ready
              return true;
  
          };
  
          /**
           * Renders this frame, calling the associated callback, if any, after
           * the frame is complete. This function MUST only be called when no
           * blocked tasks exist. Calling this function with blocked tasks
           * will result in undefined behavior.
           */
          this.flush = function() {
  
              // Draw all pending tasks.
              for (var i=0; i < tasks.length; i++)
                  tasks[i].execute();
  
              // Call callback
              if (callback) callback();
  
          };
  
      }
  
      /**
       * A container for an task handler. Each operation which must be ordered
       * is associated with a Task that goes into a task queue. Tasks in this
       * queue are executed in order once their handlers are set, while Tasks 
       * without handlers block themselves and any following Tasks from running.
       *
       * @constructor
       * @private
       * @param {function} taskHandler The function to call when this task 
       *                               runs, if any.
       * @param {boolean} blocked Whether this task should start blocked.
       */
      function Task(taskHandler, blocked) {
         
          var task = this;
         
          /**
           * Whether this Task is blocked.
           * 
           * @type {boolean}
           */
          this.blocked = blocked;
  
          /**
           * Unblocks this Task, allowing it to run.
           */
          this.unblock = function() {
              if (task.blocked) {
                  task.blocked = false;
                  __flush_frames();
              }
          };
  
          /**
           * Calls the handler associated with this task IMMEDIATELY. This
           * function does not track whether this task is marked as blocked.
           * Enforcing the blocked status of tasks is up to the caller.
           */
          this.execute = function() {
              if (taskHandler) taskHandler();
          };
  
      }
  
      /**
       * Schedules a task for future execution. The given handler will execute
       * immediately after all previous tasks upon frame flush, unless this
       * task is blocked. If any tasks is blocked, the entire frame will not
       * render (and no tasks within will execute) until all tasks are unblocked.
       * 
       * @private
       * @param {function} handler The function to call when possible, if any.
       * @param {boolean} blocked Whether the task should start blocked.
       * @returns {Task} The Task created and added to the queue for future
       *                 running.
       */
      function scheduleTask(handler, blocked) {
          var task = new Task(handler, blocked);
          tasks.push(task);
          return task;
      }
  
      /**
       * Returns the element which contains the Guacamole display.
       * 
       * @return {Element} The element containing the Guacamole display.
       */
      this.getElement = function() {
          return bounds;
      };
  
      /**
       * Returns the width of this display.
       * 
       * @return {Number} The width of this display;
       */
      this.getWidth = function() {
          return displayWidth;
      };
  
      /**
       * Returns the height of this display.
       * 
       * @return {Number} The height of this display;
       */
      this.getHeight = function() {
          return displayHeight;
      };
  
      /**
       * Returns the default layer of this display. Each Guacamole display always
       * has at least one layer. Other layers can optionally be created within
       * this layer, but the default layer cannot be removed and is the absolute
       * ancestor of all other layers.
       * 
       * @return {Guacamole.Display.VisibleLayer} The default layer.
       */
      this.getDefaultLayer = function() {
          return default_layer;
      };
  
      /**
       * Returns the cursor layer of this display. Each Guacamole display contains
       * a layer for the image of the mouse cursor. This layer is a special case
       * and exists above all other layers, similar to the hardware mouse cursor.
       * 
       * @return {Guacamole.Display.VisibleLayer} The cursor layer.
       */
      this.getCursorLayer = function() {
          return cursor;
      };
  
      /**
       * Creates a new layer. The new layer will be a direct child of the default
       * layer, but can be moved to be a child of any other layer. Layers returned
       * by this function are visible.
       * 
       * @return {Guacamole.Display.VisibleLayer} The newly-created layer.
       */
      this.createLayer = function() {
          var layer = new Guacamole.Display.VisibleLayer(displayWidth, displayHeight);
          layer.move(default_layer, 0, 0, 0);
          return layer;
      };
  
      /**
       * Creates a new buffer. Buffers are invisible, off-screen surfaces. They
       * are implemented in the same manner as layers, but do not provide the
       * same nesting semantics.
       * 
       * @return {Guacamole.Layer} The newly-created buffer.
       */
      this.createBuffer = function() {
          var buffer = new Guacamole.Layer(0, 0);
          buffer.autosize = 1;
          return buffer;
      };
  
      /**
       * Flush all pending draw tasks, if possible, as a new frame. If the entire
       * frame is not ready, the flush will wait until all required tasks are
       * unblocked.
       * 
       * @param {function} callback The function to call when this frame is
       *                            flushed. This may happen immediately, or
       *                            later when blocked tasks become unblocked.
       */
      this.flush = function(callback) {
  
          // Add frame, reset tasks
          frames.push(new Frame(callback, tasks));
          tasks = [];
  
          // Attempt flush
          __flush_frames();
  
      };
  
      /**
       * Sets the hotspot and image of the mouse cursor displayed within the
       * Guacamole display.
       * 
       * @param {Number} hotspotX The X coordinate of the cursor hotspot.
       * @param {Number} hotspotY The Y coordinate of the cursor hotspot.
       * @param {Guacamole.Layer} layer The source layer containing the data which
       *                                should be used as the mouse cursor image.
       * @param {Number} srcx The X coordinate of the upper-left corner of the
       *                      rectangle within the source layer's coordinate
       *                      space to copy data from.
       * @param {Number} srcy The Y coordinate of the upper-left corner of the
       *                      rectangle within the source layer's coordinate
       *                      space to copy data from.
       * @param {Number} srcw The width of the rectangle within the source layer's
       *                      coordinate space to copy data from.
       * @param {Number} srch The height of the rectangle within the source
       *                      layer's coordinate space to copy data from.
  
       */
      this.setCursor = function(hotspotX, hotspotY, layer, srcx, srcy, srcw, srch) {
          scheduleTask(function __display_set_cursor() {
  
              // Set hotspot
              guac_display.cursorHotspotX = hotspotX;
              guac_display.cursorHotspotY = hotspotY;
  
              // Reset cursor size
              cursor.resize(srcw, srch);
  
              // Draw cursor to cursor layer
              cursor.copy(layer, srcx, srcy, srcw, srch, 0, 0);
              guac_display.moveCursor(guac_display.cursorX, guac_display.cursorY);
  
              // Fire cursor change event
              if (guac_display.oncursor)
                  guac_display.oncursor(cursor.toCanvas(), hotspotX, hotspotY);
  
          });
      };
  
      /**
       * Sets whether the software-rendered cursor is shown. This cursor differs
       * from the hardware cursor in that it is built into the Guacamole.Display,
       * and relies on its own Guacamole layer to render.
       *
       * @param {Boolean} [shown=true] Whether to show the software cursor.
       */
      this.showCursor = function(shown) {
  
          var element = cursor.getElement();
          var parent = element.parentNode;
  
          // Remove from DOM if hidden
          if (shown === false) {
              if (parent)
                  parent.removeChild(element);
          }
  
          // Otherwise, ensure cursor is child of display
          else if (parent !== display)
              display.appendChild(element);
  
      };
  
      /**
       * Sets the location of the local cursor to the given coordinates. For the
       * sake of responsiveness, this function performs its action immediately.
       * Cursor motion is not maintained within atomic frames.
       * 
       * @param {Number} x The X coordinate to move the cursor to.
       * @param {Number} y The Y coordinate to move the cursor to.
       */
      this.moveCursor = function(x, y) {
  
          // Move cursor layer
          cursor.translate(x - guac_display.cursorHotspotX,
                           y - guac_display.cursorHotspotY);
  
          // Update stored position
          guac_display.cursorX = x;
          guac_display.cursorY = y;
  
      };
  
      /**
       * Changes the size of the given Layer to the given width and height.
       * Resizing is only attempted if the new size provided is actually different
       * from the current size.
       * 
       * @param {Guacamole.Layer} layer The layer to resize.
       * @param {Number} width The new width.
       * @param {Number} height The new height.
       */
      this.resize = function(layer, width, height) {
          scheduleTask(function __display_resize() {
  
              layer.resize(width, height);
  
              // Resize display if default layer is resized
              if (layer === default_layer) {
  
                  // Update (set) display size
                  displayWidth = width;
                  displayHeight = height;
                  display.style.width = displayWidth + "px";
                  display.style.height = displayHeight + "px";
  
                  // Update bounds size
                  bounds.style.width = (displayWidth*displayScale) + "px";
                  bounds.style.height = (displayHeight*displayScale) + "px";
  
                  // Notify of resize
                  if (guac_display.onresize)
                      guac_display.onresize(width, height);
  
              }
  
          });
      };
  
      /**
       * Draws the specified image at the given coordinates. The image specified
       * must already be loaded.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       * @param {Number} x The destination X coordinate.
       * @param {Number} y The destination Y coordinate.
       * @param {Image} image The image to draw. Note that this is an Image
       *                      object - not a URL.
       */
      this.drawImage = function(layer, x, y, image) {
          scheduleTask(function __display_drawImage() {
              layer.drawImage(x, y, image);
          });
      };
  
      /**
       * Draws the image contained within the specified Blob at the given
       * coordinates. The Blob specified must already be populated with image
       * data.
       *
       * @param {Guacamole.Layer} layer
       *     The layer to draw upon.
       *
       * @param {Number} x
       *     The destination X coordinate.
       *
       * @param {Number} y
       *     The destination Y coordinate.
       *
       * @param {Blob} blob
       *     The Blob containing the image data to draw.
       */
      this.drawBlob = function(layer, x, y, blob) {
  
          // Create URL for blob
          var url = URL.createObjectURL(blob);
  
          // Draw and free blob URL when ready
          var task = scheduleTask(function __display_drawBlob() {
  
              // Draw the image only if it loaded without errors
              if (image.width && image.height)
                  layer.drawImage(x, y, image);
  
              // Blob URL no longer needed
              URL.revokeObjectURL(url);
  
          }, true);
  
          // Load image from URL
          var image = new Image();
          image.onload = task.unblock;
          image.onerror = task.unblock;
          image.src = url;
  
      };
  
      /**
       * Draws the image at the specified URL at the given coordinates. The image
       * will be loaded automatically, and this and any future operations will
       * wait for the image to finish loading.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       * @param {Number} x The destination X coordinate.
       * @param {Number} y The destination Y coordinate.
       * @param {String} url The URL of the image to draw.
       */
      this.draw = function(layer, x, y, url) {
  
          var task = scheduleTask(function __display_draw() {
  
              // Draw the image only if it loaded without errors
              if (image.width && image.height)
                  layer.drawImage(x, y, image);
  
          }, true);
  
          var image = new Image();
          image.onload = task.unblock;
          image.onerror = task.unblock;
          image.src = url;
  
      };
  
      /**
       * Plays the video at the specified URL within this layer. The video
       * will be loaded automatically, and this and any future operations will
       * wait for the video to finish loading. Future operations will not be
       * executed until the video finishes playing.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       * @param {String} mimetype The mimetype of the video to play.
       * @param {Number} duration The duration of the video in milliseconds.
       * @param {String} url The URL of the video to play.
       */
      this.play = function(layer, mimetype, duration, url) {
  
          // Start loading the video
          var video = document.createElement("video");
          video.type = mimetype;
          video.src = url;
  
          // Start copying frames when playing
          video.addEventListener("play", function() {
              
              function render_callback() {
                  layer.drawImage(0, 0, video);
                  if (!video.ended)
                      window.setTimeout(render_callback, 20);
              }
              
              render_callback();
              
          }, false);
  
          scheduleTask(video.play);
  
      };
  
      /**
       * Transfer a rectangle of image data from one Layer to this Layer using the
       * specified transfer function.
       * 
       * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
       * @param {Number} srcx The X coordinate of the upper-left corner of the
       *                      rectangle within the source Layer's coordinate
       *                      space to copy data from.
       * @param {Number} srcy The Y coordinate of the upper-left corner of the
       *                      rectangle within the source Layer's coordinate
       *                      space to copy data from.
       * @param {Number} srcw The width of the rectangle within the source Layer's
       *                      coordinate space to copy data from.
       * @param {Number} srch The height of the rectangle within the source
       *                      Layer's coordinate space to copy data from.
       * @param {Guacamole.Layer} dstLayer The layer to draw upon.
       * @param {Number} x The destination X coordinate.
       * @param {Number} y The destination Y coordinate.
       * @param {Function} transferFunction The transfer function to use to
       *                                    transfer data from source to
       *                                    destination.
       */
      this.transfer = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y, transferFunction) {
          scheduleTask(function __display_transfer() {
              dstLayer.transfer(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction);
          });
      };
  
      /**
       * Put a rectangle of image data from one Layer to this Layer directly
       * without performing any alpha blending. Simply copy the data.
       * 
       * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
       * @param {Number} srcx The X coordinate of the upper-left corner of the
       *                      rectangle within the source Layer's coordinate
       *                      space to copy data from.
       * @param {Number} srcy The Y coordinate of the upper-left corner of the
       *                      rectangle within the source Layer's coordinate
       *                      space to copy data from.
       * @param {Number} srcw The width of the rectangle within the source Layer's
       *                      coordinate space to copy data from.
       * @param {Number} srch The height of the rectangle within the source
       *                      Layer's coordinate space to copy data from.
       * @param {Guacamole.Layer} dstLayer The layer to draw upon.
       * @param {Number} x The destination X coordinate.
       * @param {Number} y The destination Y coordinate.
       */
      this.put = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y) {
          scheduleTask(function __display_put() {
              dstLayer.put(srcLayer, srcx, srcy, srcw, srch, x, y);
          });
      };
  
      /**
       * Copy a rectangle of image data from one Layer to this Layer. This
       * operation will copy exactly the image data that will be drawn once all
       * operations of the source Layer that were pending at the time this
       * function was called are complete. This operation will not alter the
       * size of the source Layer even if its autosize property is set to true.
       * 
       * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
       * @param {Number} srcx The X coordinate of the upper-left corner of the
       *                      rectangle within the source Layer's coordinate
       *                      space to copy data from.
       * @param {Number} srcy The Y coordinate of the upper-left corner of the
       *                      rectangle within the source Layer's coordinate
       *                      space to copy data from.
       * @param {Number} srcw The width of the rectangle within the source Layer's
       *                      coordinate space to copy data from.
       * @param {Number} srch The height of the rectangle within the source
       *                      Layer's coordinate space to copy data from.
       * @param {Guacamole.Layer} dstLayer The layer to draw upon.
       * @param {Number} x The destination X coordinate.
       * @param {Number} y The destination Y coordinate.
       */
      this.copy = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y) {
          scheduleTask(function __display_copy() {
              dstLayer.copy(srcLayer, srcx, srcy, srcw, srch, x, y);
          });
      };
  
      /**
       * Starts a new path at the specified point.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       * @param {Number} x The X coordinate of the point to draw.
       * @param {Number} y The Y coordinate of the point to draw.
       */
      this.moveTo = function(layer, x, y) {
          scheduleTask(function __display_moveTo() {
              layer.moveTo(x, y);
          });
      };
  
      /**
       * Add the specified line to the current path.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       * @param {Number} x The X coordinate of the endpoint of the line to draw.
       * @param {Number} y The Y coordinate of the endpoint of the line to draw.
       */
      this.lineTo = function(layer, x, y) {
          scheduleTask(function __display_lineTo() {
              layer.lineTo(x, y);
          });
      };
  
      /**
       * Add the specified arc to the current path.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       * @param {Number} x The X coordinate of the center of the circle which
       *                   will contain the arc.
       * @param {Number} y The Y coordinate of the center of the circle which
       *                   will contain the arc.
       * @param {Number} radius The radius of the circle.
       * @param {Number} startAngle The starting angle of the arc, in radians.
       * @param {Number} endAngle The ending angle of the arc, in radians.
       * @param {Boolean} negative Whether the arc should be drawn in order of
       *                           decreasing angle.
       */
      this.arc = function(layer, x, y, radius, startAngle, endAngle, negative) {
          scheduleTask(function __display_arc() {
              layer.arc(x, y, radius, startAngle, endAngle, negative);
          });
      };
  
      /**
       * Starts a new path at the specified point.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       * @param {Number} cp1x The X coordinate of the first control point.
       * @param {Number} cp1y The Y coordinate of the first control point.
       * @param {Number} cp2x The X coordinate of the second control point.
       * @param {Number} cp2y The Y coordinate of the second control point.
       * @param {Number} x The X coordinate of the endpoint of the curve.
       * @param {Number} y The Y coordinate of the endpoint of the curve.
       */
      this.curveTo = function(layer, cp1x, cp1y, cp2x, cp2y, x, y) {
          scheduleTask(function __display_curveTo() {
              layer.curveTo(cp1x, cp1y, cp2x, cp2y, x, y);
          });
      };
  
      /**
       * Closes the current path by connecting the end point with the start
       * point (if any) with a straight line.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       */
      this.close = function(layer) {
          scheduleTask(function __display_close() {
              layer.close();
          });
      };
  
      /**
       * Add the specified rectangle to the current path.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       * @param {Number} x The X coordinate of the upper-left corner of the
       *                   rectangle to draw.
       * @param {Number} y The Y coordinate of the upper-left corner of the
       *                   rectangle to draw.
       * @param {Number} w The width of the rectangle to draw.
       * @param {Number} h The height of the rectangle to draw.
       */
      this.rect = function(layer, x, y, w, h) {
          scheduleTask(function __display_rect() {
              layer.rect(x, y, w, h);
          });
      };
  
      /**
       * Clip all future drawing operations by the current path. The current path
       * is implicitly closed. The current path can continue to be reused
       * for other operations (such as fillColor()) but a new path will be started
       * once a path drawing operation (path() or rect()) is used.
       * 
       * @param {Guacamole.Layer} layer The layer to affect.
       */
      this.clip = function(layer) {
          scheduleTask(function __display_clip() {
              layer.clip();
          });
      };
  
      /**
       * Stroke the current path with the specified color. The current path
       * is implicitly closed. The current path can continue to be reused
       * for other operations (such as clip()) but a new path will be started
       * once a path drawing operation (path() or rect()) is used.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       * @param {String} cap The line cap style. Can be "round", "square",
       *                     or "butt".
       * @param {String} join The line join style. Can be "round", "bevel",
       *                      or "miter".
       * @param {Number} thickness The line thickness in pixels.
       * @param {Number} r The red component of the color to fill.
       * @param {Number} g The green component of the color to fill.
       * @param {Number} b The blue component of the color to fill.
       * @param {Number} a The alpha component of the color to fill.
       */
      this.strokeColor = function(layer, cap, join, thickness, r, g, b, a) {
          scheduleTask(function __display_strokeColor() {
              layer.strokeColor(cap, join, thickness, r, g, b, a);
          });
      };
  
      /**
       * Fills the current path with the specified color. The current path
       * is implicitly closed. The current path can continue to be reused
       * for other operations (such as clip()) but a new path will be started
       * once a path drawing operation (path() or rect()) is used.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       * @param {Number} r The red component of the color to fill.
       * @param {Number} g The green component of the color to fill.
       * @param {Number} b The blue component of the color to fill.
       * @param {Number} a The alpha component of the color to fill.
       */
      this.fillColor = function(layer, r, g, b, a) {
          scheduleTask(function __display_fillColor() {
              layer.fillColor(r, g, b, a);
          });
      };
  
      /**
       * Stroke the current path with the image within the specified layer. The
       * image data will be tiled infinitely within the stroke. The current path
       * is implicitly closed. The current path can continue to be reused
       * for other operations (such as clip()) but a new path will be started
       * once a path drawing operation (path() or rect()) is used.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       * @param {String} cap The line cap style. Can be "round", "square",
       *                     or "butt".
       * @param {String} join The line join style. Can be "round", "bevel",
       *                      or "miter".
       * @param {Number} thickness The line thickness in pixels.
       * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
       *                                   within the stroke.
       */
      this.strokeLayer = function(layer, cap, join, thickness, srcLayer) {
          scheduleTask(function __display_strokeLayer() {
              layer.strokeLayer(cap, join, thickness, srcLayer);
          });
      };
  
      /**
       * Fills the current path with the image within the specified layer. The
       * image data will be tiled infinitely within the stroke. The current path
       * is implicitly closed. The current path can continue to be reused
       * for other operations (such as clip()) but a new path will be started
       * once a path drawing operation (path() or rect()) is used.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
       *                                   within the fill.
       */
      this.fillLayer = function(layer, srcLayer) {
          scheduleTask(function __display_fillLayer() {
              layer.fillLayer(srcLayer);
          });
      };
  
      /**
       * Push current layer state onto stack.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       */
      this.push = function(layer) {
          scheduleTask(function __display_push() {
              layer.push();
          });
      };
  
      /**
       * Pop layer state off stack.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       */
      this.pop = function(layer) {
          scheduleTask(function __display_pop() {
              layer.pop();
          });
      };
  
      /**
       * Reset the layer, clearing the stack, the current path, and any transform
       * matrix.
       * 
       * @param {Guacamole.Layer} layer The layer to draw upon.
       */
      this.reset = function(layer) {
          scheduleTask(function __display_reset() {
              layer.reset();
          });
      };
  
      /**
       * Sets the given affine transform (defined with six values from the
       * transform's matrix).
       * 
       * @param {Guacamole.Layer} layer The layer to modify.
       * @param {Number} a The first value in the affine transform's matrix.
       * @param {Number} b The second value in the affine transform's matrix.
       * @param {Number} c The third value in the affine transform's matrix.
       * @param {Number} d The fourth value in the affine transform's matrix.
       * @param {Number} e The fifth value in the affine transform's matrix.
       * @param {Number} f The sixth value in the affine transform's matrix.
       */
      this.setTransform = function(layer, a, b, c, d, e, f) {
          scheduleTask(function __display_setTransform() {
              layer.setTransform(a, b, c, d, e, f);
          });
      };
  
      /**
       * Applies the given affine transform (defined with six values from the
       * transform's matrix).
       * 
       * @param {Guacamole.Layer} layer The layer to modify.
       * @param {Number} a The first value in the affine transform's matrix.
       * @param {Number} b The second value in the affine transform's matrix.
       * @param {Number} c The third value in the affine transform's matrix.
       * @param {Number} d The fourth value in the affine transform's matrix.
       * @param {Number} e The fifth value in the affine transform's matrix.
       * @param {Number} f The sixth value in the affine transform's matrix.
       */
      this.transform = function(layer, a, b, c, d, e, f) {
          scheduleTask(function __display_transform() {
              layer.transform(a, b, c, d, e, f);
          });
      };
  
      /**
       * Sets the channel mask for future operations on this Layer.
       * 
       * The channel mask is a Guacamole-specific compositing operation identifier
       * with a single bit representing each of four channels (in order): source
       * image where destination transparent, source where destination opaque,
       * destination where source transparent, and destination where source
       * opaque.
       * 
       * @param {Guacamole.Layer} layer The layer to modify.
       * @param {Number} mask The channel mask for future operations on this
       *                      Layer.
       */
      this.setChannelMask = function(layer, mask) {
          scheduleTask(function __display_setChannelMask() {
              layer.setChannelMask(mask);
          });
      };
  
      /**
       * Sets the miter limit for stroke operations using the miter join. This
       * limit is the maximum ratio of the size of the miter join to the stroke
       * width. If this ratio is exceeded, the miter will not be drawn for that
       * joint of the path.
       * 
       * @param {Guacamole.Layer} layer The layer to modify.
       * @param {Number} limit The miter limit for stroke operations using the
       *                       miter join.
       */
      this.setMiterLimit = function(layer, limit) {
          scheduleTask(function __display_setMiterLimit() {
              layer.setMiterLimit(limit);
          });
      };
  
      /**
       * Removes the given layer container entirely, such that it is no longer
       * contained within its parent layer, if any.
       *
       * @param {Guacamole.Display.VisibleLayer} layer
       *     The layer being removed from its parent.
       */
      this.dispose = function dispose(layer) {
          scheduleTask(function disposeLayer() {
              layer.dispose();
          });
      };
  
      /**
       * Applies the given affine transform (defined with six values from the
       * transform's matrix) to the given layer.
       *
       * @param {Guacamole.Display.VisibleLayer} layer
       *     The layer being distorted.
       *
       * @param {Number} a
       *     The first value in the affine transform's matrix.
       *
       * @param {Number} b
       *     The second value in the affine transform's matrix.
       *
       * @param {Number} c
       *     The third value in the affine transform's matrix.
       *
       * @param {Number} d
       *     The fourth value in the affine transform's matrix.
       *
       * @param {Number} e
       *     The fifth value in the affine transform's matrix.
       *
       * @param {Number} f
       *     The sixth value in the affine transform's matrix.
       */
      this.distort = function distort(layer, a, b, c, d, e, f) {
          scheduleTask(function distortLayer() {
              layer.distort(a, b, c, d, e, f);
          });
      };
  
      /**
       * Moves the upper-left corner of the given layer to the given X and Y
       * coordinate, sets the Z stacking order, and reparents the layer
       * to the given parent layer.
       *
       * @param {Guacamole.Display.VisibleLayer} layer
       *     The layer being moved.
       *
       * @param {Guacamole.Display.VisibleLayer} parent
       *     The parent to set.
       *
       * @param {Number} x
       *     The X coordinate to move to.
       *
       * @param {Number} y
       *     The Y coordinate to move to.
       *
       * @param {Number} z
       *     The Z coordinate to move to.
       */
      this.move = function move(layer, parent, x, y, z) {
          scheduleTask(function moveLayer() {
              layer.move(parent, x, y, z);
          });
      };
  
      /**
       * Sets the opacity of the given layer to the given value, where 255 is
       * fully opaque and 0 is fully transparent.
       *
       * @param {Guacamole.Display.VisibleLayer} layer
       *     The layer whose opacity should be set.
       *
       * @param {Number} alpha
       *     The opacity to set.
       */
      this.shade = function shade(layer, alpha) {
          scheduleTask(function shadeLayer() {
              layer.shade(alpha);
          });
      };
  
      /**
       * Sets the scale of the client display element such that it renders at
       * a relatively smaller or larger size, without affecting the true
       * resolution of the display.
       *
       * @param {Number} scale The scale to resize to, where 1.0 is normal
       *                       size (1:1 scale).
       */
      this.scale = function(scale) {
  
          display.style.transform =
          display.style.WebkitTransform =
          display.style.MozTransform =
          display.style.OTransform =
          display.style.msTransform =
  
              "scale(" + scale + "," + scale + ")";
  
          displayScale = scale;
  
          // Update bounds size
          bounds.style.width = (displayWidth*displayScale) + "px";
          bounds.style.height = (displayHeight*displayScale) + "px";
  
      };
  
      /**
       * Returns the scale of the display.
       *
       * @return {Number} The scale of the display.
       */
      this.getScale = function() {
          return displayScale;
      };
  
      /**
       * Returns a canvas element containing the entire display, with all child
       * layers composited within.
       *
       * @return {HTMLCanvasElement} A new canvas element containing a copy of
       *                             the display.
       */
      this.flatten = function() {
         
          // Get destination canvas
          var canvas = document.createElement("canvas");
          canvas.width = default_layer.width;
          canvas.height = default_layer.height;
  
          var context = canvas.getContext("2d");
  
          // Returns sorted array of children
          function get_children(layer) {
  
              // Build array of children
              var children = [];
              for (var index in layer.children)
                  children.push(layer.children[index]);
  
              // Sort
              children.sort(function children_comparator(a, b) {
  
                  // Compare based on Z order
                  var diff = a.z - b.z;
                  if (diff !== 0)
                      return diff;
  
                  // If Z order identical, use document order
                  var a_element = a.getElement();
                  var b_element = b.getElement();
                  var position = b_element.compareDocumentPosition(a_element);
  
                  if (position & Node.DOCUMENT_POSITION_PRECEDING) return -1;
                  if (position & Node.DOCUMENT_POSITION_FOLLOWING) return  1;
  
                  // Otherwise, assume same
                  return 0;
  
              });
  
              // Done
              return children;
  
          }
  
          // Draws the contents of the given layer at the given coordinates
          function draw_layer(layer, x, y) {
  
              // Draw layer
              if (layer.width > 0 && layer.height > 0) {
  
                  // Save and update alpha
                  var initial_alpha = context.globalAlpha;
                  context.globalAlpha *= layer.alpha / 255.0;
  
                  // Copy data
                  context.drawImage(layer.getCanvas(), x, y);
  
                  // Draw all children
                  var children = get_children(layer);
                  for (var i=0; i<children.length; i++) {
                      var child = children[i];
                      draw_layer(child, x + child.x, y + child.y);
                  }
  
                  // Restore alpha
                  context.globalAlpha = initial_alpha;
  
              }
  
          }
  
          // Draw default layer and all children
          draw_layer(default_layer, 0, 0);
  
          // Return new canvas copy
          return canvas;
          
      };
  
  };
  
  /**
   * Simple container for Guacamole.Layer, allowing layers to be easily
   * repositioned and nested. This allows certain operations to be accelerated
   * through DOM manipulation, rather than raster operations.
   * 
   * @constructor
   * @augments Guacamole.Layer
   * @param {Number} width The width of the Layer, in pixels. The canvas element
   *                       backing this Layer will be given this width.
   * @param {Number} height The height of the Layer, in pixels. The canvas element
   *                        backing this Layer will be given this height.
   */
  Guacamole.Display.VisibleLayer = function(width, height) {
  
      Guacamole.Layer.apply(this, [width, height]);
  
      /**
       * Reference to this layer.
       * @private
       */
      var layer = this;
  
      /**
       * Identifier which uniquely identifies this layer. This is COMPLETELY
       * UNRELATED to the index of the underlying layer, which is specific
       * to the Guacamole protocol, and not relevant at this level.
       * 
       * @private
       * @type {Number}
       */
      this.__unique_id = Guacamole.Display.VisibleLayer.__next_id++;
  
      /**
       * The opacity of the layer container, where 255 is fully opaque and 0 is
       * fully transparent.
       */
      this.alpha = 0xFF;
  
      /**
       * X coordinate of the upper-left corner of this layer container within
       * its parent, in pixels.
       * @type {Number}
       */
      this.x = 0;
  
      /**
       * Y coordinate of the upper-left corner of this layer container within
       * its parent, in pixels.
       * @type {Number}
       */
      this.y = 0;
  
      /**
       * Z stacking order of this layer relative to other sibling layers.
       * @type {Number}
       */
      this.z = 0;
  
      /**
       * The affine transformation applied to this layer container. Each element
       * corresponds to a value from the transformation matrix, with the first
       * three values being the first row, and the last three values being the
       * second row. There are six values total.
       * 
       * @type {Number[]}
       */
      this.matrix = [1, 0, 0, 1, 0, 0];
  
      /**
       * The parent layer container of this layer, if any.
       * @type {Guacamole.Display.VisibleLayer}
       */
      this.parent = null;
  
      /**
       * Set of all children of this layer, indexed by layer index. This object
       * will have one property per child.
       */
      this.children = {};
  
      // Set layer position
      var canvas = layer.getCanvas();
      canvas.style.position = "absolute";
      canvas.style.left = "0px";
      canvas.style.top = "0px";
  
      // Create div with given size
      var div = document.createElement("div");
      div.appendChild(canvas);
      div.style.width = width + "px";
      div.style.height = height + "px";
      div.style.position = "absolute";
      div.style.left = "0px";
      div.style.top = "0px";
      div.style.overflow = "hidden";
  
      /**
       * Superclass resize() function.
       * @private
       */
      var __super_resize = this.resize;
  
      this.resize = function(width, height) {
  
          // Resize containing div
          div.style.width = width + "px";
          div.style.height = height + "px";
  
          __super_resize(width, height);
  
      };
    
      /**
       * Returns the element containing the canvas and any other elements
       * associated with this layer.
       * @returns {Element} The element containing this layer's canvas.
       */
      this.getElement = function() {
          return div;
      };
  
      /**
       * The translation component of this layer's transform.
       * @private
       */
      var translate = "translate(0px, 0px)"; // (0, 0)
  
      /**
       * The arbitrary matrix component of this layer's transform.
       * @private
       */
      var matrix = "matrix(1, 0, 0, 1, 0, 0)"; // Identity
  
      /**
       * Moves the upper-left corner of this layer to the given X and Y
       * coordinate.
       * 
       * @param {Number} x The X coordinate to move to.
       * @param {Number} y The Y coordinate to move to.
       */
      this.translate = function(x, y) {
  
          layer.x = x;
          layer.y = y;
  
          // Generate translation
          translate = "translate("
                          + x + "px,"
                          + y + "px)";
  
          // Set layer transform 
          div.style.transform =
          div.style.WebkitTransform =
          div.style.MozTransform =
          div.style.OTransform =
          div.style.msTransform =
  
              translate + " " + matrix;
  
      };
  
      /**
       * Moves the upper-left corner of this VisibleLayer to the given X and Y
       * coordinate, sets the Z stacking order, and reparents this VisibleLayer
       * to the given VisibleLayer.
       * 
       * @param {Guacamole.Display.VisibleLayer} parent The parent to set.
       * @param {Number} x The X coordinate to move to.
       * @param {Number} y The Y coordinate to move to.
       * @param {Number} z The Z coordinate to move to.
       */
      this.move = function(parent, x, y, z) {
  
          // Set parent if necessary
          if (layer.parent !== parent) {
  
              // Maintain relationship
              if (layer.parent)
                  delete layer.parent.children[layer.__unique_id];
              layer.parent = parent;
              parent.children[layer.__unique_id] = layer;
  
              // Reparent element
              var parent_element = parent.getElement();
              parent_element.appendChild(div);
  
          }
  
          // Set location
          layer.translate(x, y);
          layer.z = z;
          div.style.zIndex = z;
  
      };
  
      /**
       * Sets the opacity of this layer to the given value, where 255 is fully
       * opaque and 0 is fully transparent.
       * 
       * @param {Number} a The opacity to set.
       */
      this.shade = function(a) {
          layer.alpha = a;
          div.style.opacity = a/255.0;
      };
  
      /**
       * Removes this layer container entirely, such that it is no longer
       * contained within its parent layer, if any.
       */
      this.dispose = function() {
  
          // Remove from parent container
          if (layer.parent) {
              delete layer.parent.children[layer.__unique_id];
              layer.parent = null;
          }
  
          // Remove from parent element
          if (div.parentNode)
              div.parentNode.removeChild(div);
          
      };
  
      /**
       * Applies the given affine transform (defined with six values from the
       * transform's matrix).
       * 
       * @param {Number} a The first value in the affine transform's matrix.
       * @param {Number} b The second value in the affine transform's matrix.
       * @param {Number} c The third value in the affine transform's matrix.
       * @param {Number} d The fourth value in the affine transform's matrix.
       * @param {Number} e The fifth value in the affine transform's matrix.
       * @param {Number} f The sixth value in the affine transform's matrix.
       */
      this.distort = function(a, b, c, d, e, f) {
  
          // Store matrix
          layer.matrix = [a, b, c, d, e, f];
  
          // Generate matrix transformation
          matrix =
  
              /* a c e
               * b d f
               * 0 0 1
               */
      
              "matrix(" + a + "," + b + "," + c + "," + d + "," + e + "," + f + ")";
  
          // Set layer transform 
          div.style.transform =
          div.style.WebkitTransform =
          div.style.MozTransform =
          div.style.OTransform =
          div.style.msTransform =
  
              translate + " " + matrix;
  
      };
  
  };
  
  /**
   * The next identifier to be assigned to the layer container. This identifier
   * uniquely identifies each VisibleLayer, but is unrelated to the index of
   * the layer, which exists at the protocol/client level only.
   * 
   * @private
   * @type {Number}
   */
  Guacamole.Display.VisibleLayer.__next_id = 0;
  