text: /**
   * Copyright (c) 2006-2015, JGraph Ltd
   * Copyright (c) 2006-2015, Gaudenz Alder
   */
  /**
   * Class: mxCellRenderer
   * 
   * Renders cells into a document object model. The <defaultShapes> is a global
   * map of shapename, constructor pairs that is used in all instances. You can
   * get a list of all available shape names using the following code.
   * 
   * In general the cell renderer is in charge of creating, redrawing and
   * destroying the shape and label associated with a cell state, as well as
   * some other graphical objects, namely controls and overlays. The shape
   * hieararchy in the display (ie. the hierarchy in which the DOM nodes
   * appear in the document) does not reflect the cell hierarchy. The shapes
   * are a (flat) sequence of shapes and labels inside the draw pane of the
   * graph view, with some exceptions, namely the HTML labels being placed
   * directly inside the graph container for certain browsers.
   * 
   * (code)
   * mxLog.show();
   * for (var i in mxCellRenderer.prototype.defaultShapes)
   * {
   *   mxLog.debug(i);
   * }
   * (end)
   *
   * Constructor: mxCellRenderer
   * 
   * Constructs a new cell renderer with the following built-in shapes:
   * arrow, rectangle, ellipse, rhombus, image, line, label, cylinder,
   * swimlane, connector, actor and cloud.
   */
  function mxCellRenderer() { };
  
  /**
   * Variable: defaultEdgeShape
   * 
   * Defines the default shape for edges. Default is <mxConnector>.
   */
  mxCellRenderer.prototype.defaultEdgeShape = mxConnector;
  
  /**
   * Variable: defaultVertexShape
   * 
   * Defines the default shape for vertices. Default is <mxRectangleShape>.
   */
  mxCellRenderer.prototype.defaultVertexShape = mxRectangleShape;
  
  /**
   * Variable: defaultTextShape
   * 
   * Defines the default shape for labels. Default is <mxText>.
   */
  mxCellRenderer.prototype.defaultTextShape = mxText;
  
  /**
   * Variable: legacyControlPosition
   * 
   * Specifies if the folding icon should ignore the horizontal
   * orientation of a swimlane. Default is true.
   */
  mxCellRenderer.prototype.legacyControlPosition = true;
  
  /**
   * Variable: legacySpacing
   * 
   * Specifies if spacing and label position should be ignored if overflow is
   * fill or width. Default is true for backwards compatiblity.
   */
  mxCellRenderer.prototype.legacySpacing = true;
  
  /**
   * Variable: defaultShapes
   * 
   * Static array that contains the globally registered shapes which are
   * known to all instances of this class. For adding new shapes you should
   * use the static <mxCellRenderer.registerShape> function.
   */
  mxCellRenderer.prototype.defaultShapes = new Object();
  
  /**
   * Variable: antiAlias
   * 
   * Anti-aliasing option for new shapes. Default is true.
   */
  mxCellRenderer.prototype.antiAlias = true;
  
  /**
   * Variable: forceControlClickHandler
   * 
   * Specifies if the enabled state of the graph should be ignored in the control
   * click handler (to allow folding in disabled graphs). Default is false.
   */
  mxCellRenderer.prototype.forceControlClickHandler = false;
  
  /**
   * Function: registerShape
   * 
   * Registers the given constructor under the specified key in this instance
   * of the renderer.
   * 
   * Example:
   * 
   * (code)
   * mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
   * (end)
   * 
   * Parameters:
   * 
   * key - String representing the shape name.
   * shape - Constructor of the <mxShape> subclass.
   */
  mxCellRenderer.registerShape = function(key, shape)
  {
  	mxCellRenderer.prototype.defaultShapes[key] = shape;
  };
  
  // Adds default shapes into the default shapes array
  mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
  mxCellRenderer.registerShape(mxConstants.SHAPE_ELLIPSE, mxEllipse);
  mxCellRenderer.registerShape(mxConstants.SHAPE_RHOMBUS, mxRhombus);
  mxCellRenderer.registerShape(mxConstants.SHAPE_CYLINDER, mxCylinder);
  mxCellRenderer.registerShape(mxConstants.SHAPE_CONNECTOR, mxConnector);
  mxCellRenderer.registerShape(mxConstants.SHAPE_ACTOR, mxActor);
  mxCellRenderer.registerShape(mxConstants.SHAPE_TRIANGLE, mxTriangle);
  mxCellRenderer.registerShape(mxConstants.SHAPE_HEXAGON, mxHexagon);
  mxCellRenderer.registerShape(mxConstants.SHAPE_CLOUD, mxCloud);
  mxCellRenderer.registerShape(mxConstants.SHAPE_LINE, mxLine);
  mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW, mxArrow);
  mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW_CONNECTOR, mxArrowConnector);
  mxCellRenderer.registerShape(mxConstants.SHAPE_DOUBLE_ELLIPSE, mxDoubleEllipse);
  mxCellRenderer.registerShape(mxConstants.SHAPE_SWIMLANE, mxSwimlane);
  mxCellRenderer.registerShape(mxConstants.SHAPE_IMAGE, mxImageShape);
  mxCellRenderer.registerShape(mxConstants.SHAPE_LABEL, mxLabel);
  
  /**
   * Function: initializeShape
   * 
   * Initializes the shape in the given state by calling its init method with
   * the correct container after configuring it using <configureShape>.
   * 
   * Parameters:
   * 
   * state - <mxCellState> for which the shape should be initialized.
   */
  mxCellRenderer.prototype.initializeShape = function(state)
  {
  	state.shape.dialect = state.view.graph.dialect;
  	this.configureShape(state);
  	state.shape.init(state.view.getDrawPane());
  };
  
  /**
   * Function: createShape
   * 
   * Creates and returns the shape for the given cell state.
   * 
   * Parameters:
   * 
   * state - <mxCellState> for which the shape should be created.
   */
  mxCellRenderer.prototype.createShape = function(state)
  {
  	var shape = null;
  	
  	if (state.style != null)
  	{
  		// Checks if there is a stencil for the name and creates
  		// a shape instance for the stencil if one exists
  		var stencil = mxStencilRegistry.getStencil(state.style[mxConstants.STYLE_SHAPE]);
  		
  		if (stencil != null)
  		{
  			shape = new mxShape(stencil);
  		}
  		else
  		{
  			var ctor = this.getShapeConstructor(state);
  			shape = new ctor();
  		}
  	}
  	
  	return shape;
  };
  
  /**
   * Function: createIndicatorShape
   * 
   * Creates the indicator shape for the given cell state.
   * 
   * Parameters:
   * 
   * state - <mxCellState> for which the indicator shape should be created.
   */
  mxCellRenderer.prototype.createIndicatorShape = function(state)
  {
  	state.shape.indicatorShape = this.getShape(state.view.graph.getIndicatorShape(state));
  };
  
  /**
   * Function: getShape
   * 
   * Returns the shape for the given name from <defaultShapes>.
   */
  mxCellRenderer.prototype.getShape = function(name)
  {
  	return (name != null) ? mxCellRenderer.prototype.defaultShapes[name] : null;
  };
  
  /**
   * Function: getShapeConstructor
   * 
   * Returns the constructor to be used for creating the shape.
   */
  mxCellRenderer.prototype.getShapeConstructor = function(state)
  {
  	var ctor = this.getShape(state.style[mxConstants.STYLE_SHAPE]);
  	
  	if (ctor == null)
  	{
  		ctor = (state.view.graph.getModel().isEdge(state.cell)) ?
  			this.defaultEdgeShape : this.defaultVertexShape;
  	}
  	
  	return ctor;
  };
  
  /**
   * Function: configureShape
   * 
   * Configures the shape for the given cell state.
   * 
   * Parameters:
   * 
   * state - <mxCellState> for which the shape should be configured.
   */
  mxCellRenderer.prototype.configureShape = function(state)
  {
  	state.shape.apply(state);
  	state.shape.image = state.view.graph.getImage(state);
  	state.shape.indicatorColor = state.view.graph.getIndicatorColor(state);
  	state.shape.indicatorStrokeColor = state.style[mxConstants.STYLE_INDICATOR_STROKECOLOR];
  	state.shape.indicatorGradientColor = state.view.graph.getIndicatorGradientColor(state);
  	state.shape.indicatorDirection = state.style[mxConstants.STYLE_INDICATOR_DIRECTION];
  	state.shape.indicatorImage = state.view.graph.getIndicatorImage(state);
  
  	this.postConfigureShape(state);
  };
  
  /**
   * Function: postConfigureShape
   * 
   * Replaces any reserved words used for attributes, eg. inherit,
   * indicated or swimlane for colors in the shape for the given state.
   * This implementation resolves these keywords on the fill, stroke
   * and gradient color keys.
   */
  mxCellRenderer.prototype.postConfigureShape = function(state)
  {
  	if (state.shape != null)
  	{
  		this.resolveColor(state, 'indicatorColor', mxConstants.STYLE_FILLCOLOR);
  		this.resolveColor(state, 'indicatorGradientColor', mxConstants.STYLE_GRADIENTCOLOR);
  		this.resolveColor(state, 'fill', mxConstants.STYLE_FILLCOLOR);
  		this.resolveColor(state, 'stroke', mxConstants.STYLE_STROKECOLOR);
  		this.resolveColor(state, 'gradient', mxConstants.STYLE_GRADIENTCOLOR);
  	}
  };
  
  /**
   * Function: resolveColor
   * 
   * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
   * the respective color on the shape.
   */
  mxCellRenderer.prototype.resolveColor = function(state, field, key)
  {
  	var value = state.shape[field];
  	var graph = state.view.graph;
  	var referenced = null;
  	
  	if (value == 'inherit')
  	{
  		referenced = graph.model.getParent(state.cell);
  	}
  	else if (value == 'swimlane')
  	{
  		if (graph.model.getTerminal(state.cell, false) != null)
  		{
  			referenced = graph.model.getTerminal(state.cell, false);
  		}
  		else
  		{
  			referenced = state.cell;
  		}
  		
  		referenced = graph.getSwimlane(referenced);
  		key = graph.swimlaneIndicatorColorAttribute;
  	}
  	else if (value == 'indicated')
  	{
  		state.shape[field] = state.shape.indicatorColor;
  	}
  	
  	if (referenced != null)
  	{
  		var rstate = graph.getView().getState(referenced);
  		state.shape[field] = null;
  
  		if (rstate != null)
  		{
  			if (rstate.shape != null && field != 'indicatorColor')
  			{
  				state.shape[field] = rstate.shape[field];
  			}
  			else
  			{
  				state.shape[field] = rstate.style[key];
  			}
  		}
  	}
  };
  
  /**
   * Function: getLabelValue
   * 
   * Returns the value to be used for the label.
   * 
   * Parameters:
   * 
   * state - <mxCellState> for which the label should be created.
   */
  mxCellRenderer.prototype.getLabelValue = function(state)
  {
  	return state.view.graph.getLabel(state.cell);
  };
  
  /**
   * Function: createLabel
   * 
   * Creates the label for the given cell state.
   * 
   * Parameters:
   * 
   * state - <mxCellState> for which the label should be created.
   */
  mxCellRenderer.prototype.createLabel = function(state, value)
  {
  	var graph = state.view.graph;
  	var isEdge = graph.getModel().isEdge(state.cell);
  	
  	if (state.style[mxConstants.STYLE_FONTSIZE] > 0 || state.style[mxConstants.STYLE_FONTSIZE] == null)
  	{
  		// Avoids using DOM node for empty labels
  		var isForceHtml = (graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value)));
  
  		state.text = new this.defaultTextShape(value, new mxRectangle(),
  				(state.style[mxConstants.STYLE_ALIGN] || mxConstants.ALIGN_CENTER),
  				graph.getVerticalAlign(state),
  				state.style[mxConstants.STYLE_FONTCOLOR],
  				state.style[mxConstants.STYLE_FONTFAMILY],
  				state.style[mxConstants.STYLE_FONTSIZE],
  				state.style[mxConstants.STYLE_FONTSTYLE],
  				state.style[mxConstants.STYLE_SPACING],
  				state.style[mxConstants.STYLE_SPACING_TOP],
  				state.style[mxConstants.STYLE_SPACING_RIGHT],
  				state.style[mxConstants.STYLE_SPACING_BOTTOM],
  				state.style[mxConstants.STYLE_SPACING_LEFT],
  				state.style[mxConstants.STYLE_HORIZONTAL],
  				state.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],
  				state.style[mxConstants.STYLE_LABEL_BORDERCOLOR],
  				graph.isWrapping(state.cell) && graph.isHtmlLabel(state.cell),
  				graph.isLabelClipped(state.cell),
  				state.style[mxConstants.STYLE_OVERFLOW],
  				state.style[mxConstants.STYLE_LABEL_PADDING],
  				mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION));
  		state.text.opacity = mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_OPACITY, 100);
  		state.text.dialect = (isForceHtml) ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;
  		state.text.style = state.style;
  		state.text.state = state;
  		this.initializeLabel(state, state.text);
  		
  		// Workaround for touch devices routing all events for a mouse gesture
  		// (down, move, up) via the initial DOM node. IE additionally redirects
  		// the event via the initial DOM node but the event source is the node
  		// under the mouse, so we need to check if this is the case and force
  		// getCellAt for the subsequent mouseMoves and the final mouseUp.
  		var forceGetCell = false;
  		
  		var getState = function(evt)
  		{
  			var result = state;
  
  			if (mxClient.IS_TOUCH || forceGetCell)
  			{
  				var x = mxEvent.getClientX(evt);
  				var y = mxEvent.getClientY(evt);
  				
  				// Dispatches the drop event to the graph which
  				// consumes and executes the source function
  				var pt = mxUtils.convertPoint(graph.container, x, y);
  				result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
  			}
  			
  			return result;
  		};
  		
  		// TODO: Add handling for special touch device gestures
  		mxEvent.addGestureListeners(state.text.node,
  			mxUtils.bind(this, function(evt)
  			{
  				if (this.isLabelEvent(state, evt))
  				{
  					graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
  					forceGetCell = graph.dialect != mxConstants.DIALECT_SVG &&
  						mxEvent.getSource(evt).nodeName == 'IMG';
  				}
  			}),
  			mxUtils.bind(this, function(evt)
  			{
  				if (this.isLabelEvent(state, evt))
  				{
  					graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
  				}
  			}),
  			mxUtils.bind(this, function(evt)
  			{
  				if (this.isLabelEvent(state, evt))
  				{
  					graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
  					forceGetCell = false;
  				}
  			})
  		);
  
  		// Uses double click timeout in mxGraph for quirks mode
  		if (graph.nativeDblClickEnabled)
  		{
  			mxEvent.addListener(state.text.node, 'dblclick',
  				mxUtils.bind(this, function(evt)
  				{
  					if (this.isLabelEvent(state, evt))
  					{
  						graph.dblClick(evt, state.cell);
  						mxEvent.consume(evt);
  					}
  				})
  			);
  		}
  	}
  };
  
  /**
   * Function: initializeLabel
   * 
   * Initiailzes the label with a suitable container.
   * 
   * Parameters:
   * 
   * state - <mxCellState> whose label should be initialized.
   */
  mxCellRenderer.prototype.initializeLabel = function(state, shape)
  {
  	if (mxClient.IS_SVG && mxClient.NO_FO && shape.dialect != mxConstants.DIALECT_SVG)
  	{
  		shape.init(state.view.graph.container);
  	}
  	else
  	{
  		shape.init(state.view.getDrawPane());
  	}
  };
  
  /**
   * Function: createCellOverlays
   * 
   * Creates the actual shape for showing the overlay for the given cell state.
   * 
   * Parameters:
   * 
   * state - <mxCellState> for which the overlay should be created.
   */
  mxCellRenderer.prototype.createCellOverlays = function(state)
  {
  	var graph = state.view.graph;
  	var overlays = graph.getCellOverlays(state.cell);
  	var dict = null;
  	
  	if (overlays != null)
  	{
  		dict = new mxDictionary();
  		
  		for (var i = 0; i < overlays.length; i++)
  		{
  			var shape = (state.overlays != null) ? state.overlays.remove(overlays[i]) : null;
  			
  			if (shape == null)
  			{
  				var tmp = new mxImageShape(new mxRectangle(), overlays[i].image.src);
  				tmp.dialect = state.view.graph.dialect;
  				tmp.preserveImageAspect = false;
  				tmp.overlay = overlays[i];
  				this.initializeOverlay(state, tmp);
  				this.installCellOverlayListeners(state, overlays[i], tmp);
  	
  				if (overlays[i].cursor != null)
  				{
  					tmp.node.style.cursor = overlays[i].cursor;
  				}
  				
  				dict.put(overlays[i], tmp);
  			}
  			else
  			{
  				dict.put(overlays[i], shape);
  			}
  		}
  	}
  	
  	// Removes unused
  	if (state.overlays != null)
  	{
  		state.overlays.visit(function(id, shape)
  		{
  			shape.destroy();
  		});
  	}
  	
  	state.overlays = dict;
  };
  
  /**
   * Function: initializeOverlay
   * 
   * Initializes the given overlay.
   * 
   * Parameters:
   * 
   * state - <mxCellState> for which the overlay should be created.
   * overlay - <mxImageShape> that represents the overlay.
   */
  mxCellRenderer.prototype.initializeOverlay = function(state, overlay)
  {
  	overlay.init(state.view.getOverlayPane());
  };
  
  /**
   * Function: installOverlayListeners
   * 
   * Installs the listeners for the given <mxCellState>, <mxCellOverlay> and
   * <mxShape> that represents the overlay.
   */
  mxCellRenderer.prototype.installCellOverlayListeners = function(state, overlay, shape)
  {
  	var graph  = state.view.graph;
  	
  	mxEvent.addListener(shape.node, 'click', function (evt)
  	{
  		if (graph.isEditing())
  		{
  			graph.stopEditing(!graph.isInvokesStopCellEditing());
  		}
  		
  		overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
  				'event', evt, 'cell', state.cell));
  	});
  	
  	mxEvent.addGestureListeners(shape.node,
  		function (evt)
  		{
  			mxEvent.consume(evt);
  		},
  		function (evt)
  		{
  			graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
  				new mxMouseEvent(evt, state));
  		});
  	
  	if (mxClient.IS_TOUCH)
  	{
  		mxEvent.addListener(shape.node, 'touchend', function (evt)
  		{
  			overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
  					'event', evt, 'cell', state.cell));
  		});
  	}
  };
  
  /**
   * Function: createControl
   * 
   * Creates the control for the given cell state.
   * 
   * Parameters:
   * 
   * state - <mxCellState> for which the control should be created.
   */
  mxCellRenderer.prototype.createControl = function(state)
  {
  	var graph = state.view.graph;
  	var image = graph.getFoldingImage(state);
  	
  	if (graph.foldingEnabled && image != null)
  	{
  		if (state.control == null)
  		{
  			var b = new mxRectangle(0, 0, image.width, image.height);
  			state.control = new mxImageShape(b, image.src);
  			state.control.preserveImageAspect = false;
  			state.control.dialect = graph.dialect;
  
  			this.initControl(state, state.control, true, this.createControlClickHandler(state));
  		}
  	}
  	else if (state.control != null)
  	{
  		state.control.destroy();
  		state.control = null;
  	}
  };
  
  /**
   * Function: createControlClickHandler
   * 
   * Hook for creating the click handler for the folding icon.
   * 
   * Parameters:
   * 
   * state - <mxCellState> whose control click handler should be returned.
   */
  mxCellRenderer.prototype.createControlClickHandler = function(state)
  {
  	var graph = state.view.graph;
  	
  	return mxUtils.bind(this, function (evt)
  	{
  		if (this.forceControlClickHandler || graph.isEnabled())
  		{
  			var collapse = !graph.isCellCollapsed(state.cell);
  			graph.foldCells(collapse, false, [state.cell], null, evt);
  			mxEvent.consume(evt);
  		}
  	});
  };
  
  /**
   * Function: initControl
   * 
   * Initializes the given control and returns the corresponding DOM node.
   * 
   * Parameters:
   * 
   * state - <mxCellState> for which the control should be initialized.
   * control - <mxShape> to be initialized.
   * handleEvents - Boolean indicating if mousedown and mousemove should fire events via the graph.
   * clickHandler - Optional function to implement clicks on the control.
   */
  mxCellRenderer.prototype.initControl = function(state, control, handleEvents, clickHandler)
  {
  	var graph = state.view.graph;
  	
  	// In the special case where the label is in HTML and the display is SVG the image
  	// should go into the graph container directly in order to be clickable. Otherwise
  	// it is obscured by the HTML label that overlaps the cell.
  	var isForceHtml = graph.isHtmlLabel(state.cell) && mxClient.NO_FO &&
  		graph.dialect == mxConstants.DIALECT_SVG;
  
  	if (isForceHtml)
  	{
  		control.dialect = mxConstants.DIALECT_PREFERHTML;
  		control.init(graph.container);
  		control.node.style.zIndex = 1;
  	}
  	else
  	{
  		control.init(state.view.getOverlayPane());
  	}
  
  	var node = control.innerNode || control.node;
  	
  	// Workaround for missing click event on iOS is to check tolerance below
  	if (clickHandler != null && !mxClient.IS_IOS)
  	{
  		if (graph.isEnabled())
  		{
  			node.style.cursor = 'pointer';
  		}
  		
  		mxEvent.addListener(node, 'click', clickHandler);
  	}
  	
  	if (handleEvents)
  	{
  		var first = null;
  
  		mxEvent.addGestureListeners(node,
  			function (evt)
  			{
  				first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
  				mxEvent.consume(evt);
  			},
  			function (evt)
  			{
  				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));
  			},
  			function (evt)
  			{
  				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, state));
  				mxEvent.consume(evt);
  			});
  		
  		// Uses capture phase for event interception to stop bubble phase
  		if (clickHandler != null && mxClient.IS_IOS)
  		{
  			node.addEventListener('touchend', function(evt)
  			{
  				if (first != null)
  				{
  					var tol = graph.tolerance;
  					
  					if (Math.abs(first.x - mxEvent.getClientX(evt)) < tol &&
  						Math.abs(first.y - mxEvent.getClientY(evt)) < tol)
  					{
  						clickHandler.call(clickHandler, evt);
  						mxEvent.consume(evt);
  					}
  				}
  			}, true);
  		}
  	}
  	
  	return node;
  };
  
  /**
   * Function: isShapeEvent
   * 
   * Returns true if the event is for the shape of the given state. This
   * implementation always returns true.
   * 
   * Parameters:
   * 
   * state - <mxCellState> whose shape fired the event.
   * evt - Mouse event which was fired.
   */
  mxCellRenderer.prototype.isShapeEvent = function(state, evt)
  {
  	return true;
  };
  
  /**
   * Function: isLabelEvent
   * 
   * Returns true if the event is for the label of the given state. This
   * implementation always returns true.
   * 
   * Parameters:
   * 
   * state - <mxCellState> whose label fired the event.
   * evt - Mouse event which was fired.
   */
  mxCellRenderer.prototype.isLabelEvent = function(state, evt)
  {
  	return true;
  };
  
  /**
   * Function: installListeners
   * 
   * Installs the event listeners for the given cell state.
   * 
   * Parameters:
   * 
   * state - <mxCellState> for which the event listeners should be isntalled.
   */
  mxCellRenderer.prototype.installListeners = function(state)
  {
  	var graph = state.view.graph;
  
  	// Workaround for touch devices routing all events for a mouse
  	// gesture (down, move, up) via the initial DOM node. Same for
  	// HTML images in all IE versions (VML images are working).
  	var getState = function(evt)
  	{
  		var result = state;
  		
  		if ((graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG') || mxClient.IS_TOUCH)
  		{
  			var x = mxEvent.getClientX(evt);
  			var y = mxEvent.getClientY(evt);
  			
  			// Dispatches the drop event to the graph which
  			// consumes and executes the source function
  			var pt = mxUtils.convertPoint(graph.container, x, y);
  			result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
  		}
  		
  		return result;
  	};
  
  	mxEvent.addGestureListeners(state.shape.node,
  		mxUtils.bind(this, function(evt)
  		{
  			if (this.isShapeEvent(state, evt))
  			{
  				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
  			}
  		}),
  		mxUtils.bind(this, function(evt)
  		{
  			if (this.isShapeEvent(state, evt))
  			{
  				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
  			}
  		}),
  		mxUtils.bind(this, function(evt)
  		{
  			if (this.isShapeEvent(state, evt))
  			{
  				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
  			}
  		})
  	);
  	
  	// Uses double click timeout in mxGraph for quirks mode
  	if (graph.nativeDblClickEnabled)
  	{
  		mxEvent.addListener(state.shape.node, 'dblclick',
  			mxUtils.bind(this, function(evt)
  			{
  				if (this.isShapeEvent(state, evt))
  				{
  					graph.dblClick(evt, state.cell);
  					mxEvent.consume(evt);
  				}
  			})
  		);
  	}
  };
  
  /**
   * Function: redrawLabel
   * 
   * Redraws the label for the given cell state.
   * 
   * Parameters:
   * 
   * state - <mxCellState> whose label should be redrawn.
   */
  mxCellRenderer.prototype.redrawLabel = function(state, forced)
  {
  	var value = this.getLabelValue(state);
  	
  	if (state.text == null && value != null && (mxUtils.isNode(value) || value.length > 0))
  	{
  		this.createLabel(state, value);
  	}
  	else if (state.text != null && (value == null || value.length == 0))
  	{
  		state.text.destroy();
  		state.text = null;
  	}
  
  	if (state.text != null)
  	{
  		var graph = state.view.graph;
  
  		// Forced is true if the style has changed, so to get the updated
  		// result in getLabelBounds we apply the new style to the shape
  		if (forced)
  		{
  			// Checks if a full repaint is needed
  			if (state.text.lastValue != null && this.isTextShapeInvalid(state, state.text))
  			{
  				// Forces a full repaint
  				state.text.lastValue = null;
  			}
  			
  			state.text.resetStyles();
  			state.text.apply(state);
  			
  			// Special case where value is obtained via hook in graph
  			state.text.valign = graph.getVerticalAlign(state);
  		}
  		
  		var bounds = this.getLabelBounds(state);
  		var wrapping = graph.isWrapping(state.cell);
  		var clipping = graph.isLabelClipped(state.cell);
  		var isForceHtml = (state.view.graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value)));
  		var dialect = (isForceHtml) ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;
  
  		// Text is a special case where change of dialect is possible at runtime
  		var overflow = state.style[mxConstants.STYLE_OVERFLOW] || 'visible';
  		
  		if (forced || state.text.value != value || state.text.isWrapping != wrapping ||
  			state.text.overflow != overflow || state.text.isClipping != clipping ||
  			state.text.scale != this.getTextScale(state) || state.text.dialect != dialect ||
  			!state.text.bounds.equals(bounds))
  		{
  			state.text.dialect = dialect;
  			state.text.value = value;
  			state.text.bounds = bounds;
  			state.text.scale = this.getTextScale(state);
  			state.text.wrap = wrapping;
  			state.text.clipped = clipping;
  			state.text.overflow = overflow;
  			
  			// Preserves visible state
  			var vis = state.text.node.style.visibility;
  			this.redrawLabelShape(state.text);
  			state.text.node.style.visibility = vis;
  		}
  	}
  };
  
  /**
   * Function: isTextShapeInvalid
   * 
   * Returns true if the style for the text shape has changed.
   * 
   * Parameters:
   * 
   * state - <mxCellState> whose label should be checked.
   * shape - <mxText> shape to be checked.
   */
  mxCellRenderer.prototype.isTextShapeInvalid = function(state, shape)
  {
  	function check(property, stylename, defaultValue)
  	{
  		return shape[property] != (state.style[stylename] || defaultValue);
  	};
  
  	return check('fontStyle', mxConstants.STYLE_FONTSTYLE, mxConstants.DEFAULT_FONTSTYLE) ||
  		check('family', mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY) ||
  		check('size', mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) ||
  		check('color', mxConstants.STYLE_FONTCOLOR, 'black') ||
  		check('align', mxConstants.STYLE_ALIGN, '') ||
  		check('valign', mxConstants.STYLE_VERTICAL_ALIGN, '') ||
  		check('spacing', mxConstants.STYLE_SPACING, 2) ||
  		check('spacingTop', mxConstants.STYLE_SPACING_TOP, 2) ||
  		check('spacingRight', mxConstants.STYLE_SPACING_RIGHT, 2) ||
  		check('spacingBottom', mxConstants.STYLE_SPACING_BOTTOM, 2) ||
  		check('spacingLeft', mxConstants.STYLE_SPACING_LEFT, 2) ||
  		check('horizontal', mxConstants.STYLE_HORIZONTAL, true) ||
  		check('background', mxConstants.STYLE_LABEL_BACKGROUNDCOLOR) ||
  		check('border', mxConstants.STYLE_LABEL_BORDERCOLOR) ||
  		check('opacity', mxConstants.STYLE_TEXT_OPACITY, 100) ||
  		check('textDirection', mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);
  };
  
  /**
   * Function: redrawLabelShape
   * 
   * Called to invoked redraw on the given text shape.
   * 
   * Parameters:
   * 
   * shape - <mxText> shape to be redrawn.
   */
  mxCellRenderer.prototype.redrawLabelShape = function(shape)
  {
  	shape.redraw();
  };
  
  /**
   * Function: getTextScale
   * 
   * Returns the scaling used for the label of the given state
   * 
   * Parameters:
   * 
   * state - <mxCellState> whose label scale should be returned.
   */
  mxCellRenderer.prototype.getTextScale = function(state)
  {
  	return state.view.scale;
  };
  
  /**
   * Function: getLabelBounds
   * 
   * Returns the bounds to be used to draw the label of the given state.
   * 
   * Parameters:
   * 
   * state - <mxCellState> whose label bounds should be returned.
   */
  mxCellRenderer.prototype.getLabelBounds = function(state)
  {
  	var graph = state.view.graph;
  	var scale = state.view.scale;
  	var isEdge = graph.getModel().isEdge(state.cell);
  	var bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y);
  
  	if (isEdge)
  	{
  		var spacing = state.text.getSpacing();
  		bounds.x += spacing.x * scale;
  		bounds.y += spacing.y * scale;
  		
  		var geo = graph.getCellGeometry(state.cell);
  		
  		if (geo != null)
  		{
  			bounds.width = Math.max(0, geo.width * scale);
  			bounds.height = Math.max(0, geo.height * scale);
  		}
  	}
  	else
  	{
  		// Inverts label position
  		if (state.text.isPaintBoundsInverted())
  		{
  			var tmp = bounds.x;
  			bounds.x = bounds.y;
  			bounds.y = tmp;
  		}
  		
  		bounds.x += state.x;
  		bounds.y += state.y;
  		
  		// Minimum of 1 fixes alignment bug in HTML labels
  		bounds.width = Math.max(1, state.width);
  		bounds.height = Math.max(1, state.height);
  
  		var sc = mxUtils.getValue(state.style, mxConstants.STYLE_STROKECOLOR, mxConstants.NONE);
  		
  		if (sc != mxConstants.NONE && sc != '')
  		{
  			var s = parseFloat(mxUtils.getValue(state.style, mxConstants.STYLE_STROKEWIDTH, 1)) * scale;
  			var dx = 1 + Math.floor((s - 1) / 2);
  			var dh = Math.floor(s + 1);
  			
  			bounds.x += dx;
  			bounds.y += dx;
  			bounds.width -= dh;
  			bounds.height -= dh;
  		}
  	}
  
  	if (state.text.isPaintBoundsInverted())
  	{
  		// Rotates around center of state
  		var t = (state.width - state.height) / 2;
  		bounds.x += t;
  		bounds.y -= t;
  		var tmp = bounds.width;
  		bounds.width = bounds.height;
  		bounds.height = tmp;
  	}
  	
  	// Shape can modify its label bounds
  	if (state.shape != null)
  	{
  		var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
  		var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
  		
  		if (hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE)
  		{
  			bounds = state.shape.getLabelBounds(bounds);
  		}
  	}
  	
  	// Label width style overrides actual label width
  	var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
  	
  	if (lw != null)
  	{
  		bounds.width = parseFloat(lw) * scale;
  	}
  	
  	if (!isEdge)
  	{
  		this.rotateLabelBounds(state, bounds);
  	}
  	
  	return bounds;
  };
  
  /**
   * Function: rotateLabelBounds
   * 
   * Adds the shape rotation to the given label bounds and
   * applies the alignment and offsets.
   * 
   * Parameters:
   * 
   * state - <mxCellState> whose label bounds should be rotated.
   * bounds - <mxRectangle> the rectangle to be rotated.
   */
  mxCellRenderer.prototype.rotateLabelBounds = function(state, bounds)
  {
  	bounds.y -= state.text.margin.y * bounds.height;
  	bounds.x -= state.text.margin.x * bounds.width;
  	
  	if (!this.legacySpacing || (state.style[mxConstants.STYLE_OVERFLOW] != 'fill' && state.style[mxConstants.STYLE_OVERFLOW] != 'width'))
  	{
  		var s = state.view.scale;
  		var spacing = state.text.getSpacing();
  		bounds.x += spacing.x * s;
  		bounds.y += spacing.y * s;
  		
  		var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
  		var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
  		var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
  		
  		bounds.width = Math.max(0, bounds.width - ((hpos == mxConstants.ALIGN_CENTER && lw == null) ? (state.text.spacingLeft * s + state.text.spacingRight * s) : 0));
  		bounds.height = Math.max(0, bounds.height - ((vpos == mxConstants.ALIGN_MIDDLE) ? (state.text.spacingTop * s + state.text.spacingBottom * s) : 0));
  	}
  
  	var theta = state.text.getTextRotation();
  
  	// Only needed if rotated around another center
  	if (theta != 0 && state != null && state.view.graph.model.isVertex(state.cell))
  	{
  		var cx = state.getCenterX();
  		var cy = state.getCenterY();
  		
  		if (bounds.x != cx || bounds.y != cy)
  		{
  			var rad = theta * (Math.PI / 180);
  			pt = mxUtils.getRotatedPoint(new mxPoint(bounds.x, bounds.y),
  					Math.cos(rad), Math.sin(rad), new mxPoint(cx, cy));
  			
  			bounds.x = pt.x;
  			bounds.y = pt.y;
  		}
  	}
  };
  
  /**
   * Function: redrawCellOverlays
   * 
   * Redraws the overlays for the given cell state.
   * 
   * Parameters:
   * 
   * state - <mxCellState> whose overlays should be redrawn.
   */
  mxCellRenderer.prototype.redrawCellOverlays = function(state, forced)
  {
  	this.createCellOverlays(state);
  
  	if (state.overlays != null)
  	{
  		var rot = mxUtils.mod(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0), 90);
          var rad = mxUtils.toRadians(rot);
          var cos = Math.cos(rad);
          var sin = Math.sin(rad);
  		
  		state.overlays.visit(function(id, shape)
  		{
  			var bounds = shape.overlay.getBounds(state);
  		
  			if (!state.view.graph.getModel().isEdge(state.cell))
  			{
  				if (state.shape != null && rot != 0)
  				{
  					var cx = bounds.getCenterX();
  					var cy = bounds.getCenterY();
  
  					var point = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin,
  			        		new mxPoint(state.getCenterX(), state.getCenterY()));
  
  			        cx = point.x;
  			        cy = point.y;
  			        bounds.x = Math.round(cx - bounds.width / 2);
  			        bounds.y = Math.round(cy - bounds.height / 2);
  				}
  			}
  			
  			if (forced || shape.bounds == null || shape.scale != state.view.scale ||
  				!shape.bounds.equals(bounds))
  			{
  				shape.bounds = bounds;
  				shape.scale = state.view.scale;
  				shape.redraw();
  			}
  		});
  	}
  };
  
  /**
   * Function: redrawControl
   * 
   * Redraws the control for the given cell state.
   * 
   * Parameters:
   * 
   * state - <mxCellState> whose control should be redrawn.
   */
  mxCellRenderer.prototype.redrawControl = function(state, forced)
  {
  	var image = state.view.graph.getFoldingImage(state);
  	
  	if (state.control != null && image != null)
  	{
  		var bounds = this.getControlBounds(state, image.width, image.height);
  		var r = (this.legacyControlPosition) ?
  				mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0) :
  				state.shape.getTextRotation();
  		var s = state.view.scale;
  		
  		if (forced || state.control.scale != s || !state.control.bounds.equals(bounds) ||
  			state.control.rotation != r)
  		{
  			state.control.rotation = r;
  			state.control.bounds = bounds;
  			state.control.scale = s;
  			
  			state.control.redraw();
  		}
  	}
  };
  
  /**
   * Function: getControlBounds
   * 
   * Returns the bounds to be used to draw the control (folding icon) of the
   * given state.
   */
  mxCellRenderer.prototype.getControlBounds = function(state, w, h)
  {
  	if (state.control != null)
  	{
  		var s = state.view.scale;
  		var cx = state.getCenterX();
  		var cy = state.getCenterY();
  	
  		if (!state.view.graph.getModel().isEdge(state.cell))
  		{
  			cx = state.x + w * s;
  			cy = state.y + h * s;
  			
  			if (state.shape != null)
  			{
  				// TODO: Factor out common code
  				var rot = state.shape.getShapeRotation();
  				
  				if (this.legacyControlPosition)
  				{
  					rot = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0);
  				}
  				else
  				{
  					if (state.shape.isPaintBoundsInverted())
  					{
  						var t = (state.width - state.height) / 2;
  						cx += t;
  						cy -= t;
  					}
  				}
  				
  				if (rot != 0)
  				{
  			        var rad = mxUtils.toRadians(rot);
  			        var cos = Math.cos(rad);
  			        var sin = Math.sin(rad);
  			        
  			        var point = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin,
  			        		new mxPoint(state.getCenterX(), state.getCenterY()));
  			        cx = point.x;
  			        cy = point.y;
  				}
  			}
  		}
  		
  		return (state.view.graph.getModel().isEdge(state.cell)) ? 
  			new mxRectangle(Math.round(cx - w / 2 * s), Math.round(cy - h / 2 * s), Math.round(w * s), Math.round(h * s))
  			: new mxRectangle(Math.round(cx - w / 2 * s), Math.round(cy - h / 2 * s), Math.round(w * s), Math.round(h * s));
  	}
  	
  	return null;
  };
  
  /**
   * Function: insertStateAfter
   * 
   * Inserts the given array of <mxShapes> after the given nodes in the DOM.
   * 
   * Parameters:
   * 
   * shapes - Array of <mxShapes> to be inserted.
   * node - Node in <drawPane> after which the shapes should be inserted.
   * htmlNode - Node in the graph container after which the shapes should be inserted that
   * will not go into the <drawPane> (eg. HTML labels without foreignObjects).
   */
  mxCellRenderer.prototype.insertStateAfter = function(state, node, htmlNode)
  {
  	var shapes = this.getShapesForState(state);
  	
  	for (var i = 0; i < shapes.length; i++)
  	{
  		if (shapes[i] != null && shapes[i].node != null)
  		{
  			var html = shapes[i].node.parentNode != state.view.getDrawPane() &&
  				shapes[i].node.parentNode != state.view.getOverlayPane();
  			var temp = (html) ? htmlNode : node;
  			
  			if (temp != null && temp.nextSibling != shapes[i].node)
  			{
  				if (temp.nextSibling == null)
  				{
  					temp.parentNode.appendChild(shapes[i].node);
  				}
  				else
  				{
  					temp.parentNode.insertBefore(shapes[i].node, temp.nextSibling);
  				}
  			}
  			else if (temp == null)
  			{
  				// Special case: First HTML node should be first sibling after canvas
  				if (shapes[i].node.parentNode == state.view.graph.container)
  				{
  					var canvas = state.view.canvas;
  					
  					while (canvas != null && canvas.parentNode != state.view.graph.container)
  					{
  						canvas = canvas.parentNode;
  					}
  					
  					if (canvas != null && canvas.nextSibling != null)
  					{
  						if (canvas.nextSibling != shapes[i].node)
  						{
  							shapes[i].node.parentNode.insertBefore(shapes[i].node, canvas.nextSibling);
  						}
  					}
  					else
  					{
  						shapes[i].node.parentNode.appendChild(shapes[i].node);
  					}
  				}
  				else if (shapes[i].node.parentNode.firstChild != null && shapes[i].node.parentNode.firstChild != shapes[i].node)
  				{
  					// Inserts the node as the first child of the parent to implement the order
  					shapes[i].node.parentNode.insertBefore(shapes[i].node, shapes[i].node.parentNode.firstChild);
  				}
  			}
  			
  			if (html)
  			{
  				htmlNode = shapes[i].node;
  			}
  			else
  			{
  				node = shapes[i].node;
  			}
  		}
  	}
  
  	return [node, htmlNode];
  };
  
  /**
   * Function: getShapesForState
   * 
   * Returns the <mxShapes> for the given cell state in the order in which they should
   * appear in the DOM.
   * 
   * Parameters:
   * 
   * state - <mxCellState> whose shapes should be returned.
   */
  mxCellRenderer.prototype.getShapesForState = function(state)
  {
  	return [state.shape, state.text, state.control];
  };
  
  /**
   * Function: redraw
   * 
   * Updates the bounds or points and scale of the shapes for the given cell
   * state. This is called in mxGraphView.validatePoints as the last step of
   * updating all cells.
   * 
   * Parameters:
   * 
   * state - <mxCellState> for which the shapes should be updated.
   * force - Optional boolean that specifies if the cell should be reconfiured
   * and redrawn without any additional checks.
   * rendering - Optional boolean that specifies if the cell should actually
   * be drawn into the DOM. If this is false then redraw and/or reconfigure
   * will not be called on the shape.
   */
  mxCellRenderer.prototype.redraw = function(state, force, rendering)
  {
  	var shapeChanged = this.redrawShape(state, force, rendering);
  	
  	if (state.shape != null && (rendering == null || rendering))
  	{
  		this.redrawLabel(state, shapeChanged);
  		this.redrawCellOverlays(state, shapeChanged);
  		this.redrawControl(state, shapeChanged);
  	}
  };
  
  /**
   * Function: redrawShape
   * 
   * Redraws the shape for the given cell state.
   * 
   * Parameters:
   * 
   * state - <mxCellState> whose label should be redrawn.
   */
  mxCellRenderer.prototype.redrawShape = function(state, force, rendering)
  {
  	var model = state.view.graph.model;
  	var shapeChanged = false;
  
  	// Forces creation of new shape if shape style has changed
  	if (state.shape != null && state.shape.style != null && state.style != null &&
  		state.shape.style[mxConstants.STYLE_SHAPE] != state.style[mxConstants.STYLE_SHAPE])
  	{
  		state.shape.destroy();
  		state.shape = null;
  	}
  	
  	if (state.shape == null && state.view.graph.container != null &&
  		state.cell != state.view.currentRoot &&
  		(model.isVertex(state.cell) || model.isEdge(state.cell)))
  	{
  		state.shape = this.createShape(state);
  		
  		if (state.shape != null)
  		{
  			state.shape.antiAlias = this.antiAlias;
  	
  			this.createIndicatorShape(state);
  			this.initializeShape(state);
  			this.createCellOverlays(state);
  			this.installListeners(state);
  			
  			// Forces a refresh of the handler of one exists
  			state.view.graph.selectionCellsHandler.updateHandler(state);
  		}
  	}
  	else if (state.shape != null && !mxUtils.equalEntries(state.shape.style, state.style))
  	{
  		state.shape.resetStyles();
  		this.configureShape(state);
  		// LATER: Ignore update for realtime to fix reset of current gesture
  		state.view.graph.selectionCellsHandler.updateHandler(state);
  		force = true;
  	}
  
  	if (state.shape != null)
  	{
  		// Handles changes of the collapse icon
  		this.createControl(state);
  		
  		// Redraws the cell if required, ignores changes to bounds if points are
  		// defined as the bounds are updated for the given points inside the shape
  		if (force || state.shape.bounds == null || state.shape.scale != state.view.scale ||
  			(state.absolutePoints == null && !state.shape.bounds.equals(state)) ||
  			(state.absolutePoints != null && !mxUtils.equalPoints(state.shape.points, state.absolutePoints)))
  		{
  			if (state.absolutePoints != null)
  			{
  				state.shape.points = state.absolutePoints.slice();
  				state.shape.bounds = null;
  			}
  			else
  			{
  				state.shape.points = null;
  				state.shape.bounds = new mxRectangle(state.x, state.y, state.width, state.height);
  			}
  
  			state.shape.scale = state.view.scale;
  			
  			if (rendering == null || rendering)
  			{
  				state.shape.redraw();
  			}
  			else
  			{
  				state.shape.updateBoundingBox();
  			}
  			
  			shapeChanged = true;
  		}
  	}
  
  	return shapeChanged;
  };
  
  /**
   * Function: destroy
   * 
   * Destroys the shapes associated with the given cell state.
   * 
   * Parameters:
   * 
   * state - <mxCellState> for which the shapes should be destroyed.
   */
  mxCellRenderer.prototype.destroy = function(state)
  {
  	if (state.shape != null)
  	{
  		if (state.text != null)
  		{		
  			state.text.destroy();
  			state.text = null;
  		}
  		
  		if (state.overlays != null)
  		{
  			state.overlays.visit(function(id, shape)
  			{
  				shape.destroy();
  			});
  			
  			state.overlays = null;
  		}
  
  		if (state.control != null)
  		{
  			state.control.destroy();
  			state.control = null;
  		}
  		
  		state.shape.destroy();
  		state.shape = null;
  	}
  };
  