Source: render/2d/kekule.render.glyphRender2D.js

/**
 * @fileoverview
 * A default implementation of 2D renderer to render small glyphs.
 * @author Partridge Jiang
 */

/*
 * requires /lan/classes.js
 * requires /utils/kekule.utils.js
 * requires /render/2d/kekule.render.render2D.js
 */

(function()
{

/** @ignore */
var PU = Kekule.Render.DrawPathUtils;
var CU = Kekule.CoordUtils;
var oneOf = Kekule.oneOf;

var NT = Kekule.Glyph.NodeType;
var PT = Kekule.Glyph.PathType;

/**
 * Class to render a of small glyph.
 * @class
 * @augments Kekule.Render.ChemObj2DRenderer
 */
Kekule.Render.BaseGlyph2DRenderer = Class.create(Kekule.Render.ChemObj2DRenderer,
/** @lends Kekule.Render.BaseGlyph2DRenderer# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Render.BaseGlyph2DRenderer',
	/** @private */
	doDraw: function($super, context, baseCoord, options)
	{
		var ops = Object.create(options);
		ops.strokeColor = options.strokeColor || options.glyphStrokeColor;
		ops.fillColor = options.fillColor || options.glyphFillColor;
		ops.strokeWidth = options.strokeWidth || options.glyphStrokeWidth;
		return $super(context, baseCoord, ops);
	}
});

/**
 * A default implementation of 2D a molecule's CTab renderer.
 * @class
 * @augments Kekule.Render.Ctab2DRenderer
 */
Kekule.Render.PathGlyphCtab2DRenderer = Class.create(Kekule.Render.Ctab2DRenderer,
/** @lends Kekule.Render.PathGlyphCtab2DRenderer# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Render.PathGlyphCtab2DRenderer',
	/** @private */
	extractGlyphDrawOptions: function(renderOptions)
	{
		var unitLength = renderOptions.unitLength || 1;
		return {
			'strokeColor': renderOptions.strokeColor || renderOptions.color || renderOptions.glyphStrokeColor,
			'fillColor': renderOptions.fillColor || renderOptions.color || renderOptions.glyphFillColor,
			'strokeWidth': (renderOptions.strokeWidth || renderOptions.glyphStrokeWidth) * unitLength
		}
	},
	/** @private */
	doDraw: function($super, context, baseCoord, options)
	{
		//console.log('do draw ctab');
		return $super(context, baseCoord, options);
	},
	/** @private */
	doDrawNode: function(context, group, node, parentChemObj, options, finalTransformOptions)
	{
		var boundInfo;
		var coord = this.getTransformedCoord2D(context, node, finalTransformOptions.allowCoordBorrow);
		var nodeType = node.getNodeType();
		if (!nodeType || nodeType === NT.LOCATION || nodeType === NT.CONTROLLER)  // no need to draw, but add bound info
		{
			boundInfo = this.createPointBoundInfo(coord);
		}

		if (boundInfo)
		{
			this.basicDrawObjectUpdated(context, node, parentChemObj, boundInfo, Kekule.Render.ObjectUpdateType.ADD);
		}
	},
	/** @private */
	doDrawConnector: function($super, context, group, connector, parentChemObj, options, finalTransformOptions)
	{
		var result = $super(context, group, connector, parentChemObj, options, finalTransformOptions);

		// if connector has control point, add bound info
		var controlPoints = connector.getControlPoints && connector.getControlPoints();
		if (controlPoints)
		{
			for (var i = 0, l = controlPoints.length; i < l; ++i)
			{
				var coord = this.getTransformedCoord2D(context, controlPoints[i], finalTransformOptions.allowCoordBorrow);
				//console.log('transformed control point coord', coord);
				var boundInfo = this.createPointBoundInfo(coord);
				this.basicDrawObjectUpdated(context, controlPoints[i], connector, boundInfo, Kekule.Render.ObjectUpdateType.ADD);
			}
		}

		return result;
	},
	/** @private */
	doDrawConnectorShape: function(context, connector, nodes, renderOptions, finalTransformOptions)
	{
		/*
		var coord1 = Object.extend({}, this.getTransformedCoord2D(context, node1, finalTransformOptions.allowCoordBorrow));
		var coord2 = Object.extend({}, this.getTransformedCoord2D(context, node2, finalTransformOptions.allowCoordBorrow));
		*/
		var AS = Kekule.Glyph.ArrowSide;

		var node1 = nodes[0];
		var node2 = nodes[1];
		var coord1 = CU.clone(this.getTransformedCoord2D(context, node1, finalTransformOptions.allowCoordBorrow));
		var coord2 = CU.clone(this.getTransformedCoord2D(context, node2, finalTransformOptions.allowCoordBorrow));
		var drawOptions = this.extractGlyphDrawOptions(renderOptions);

		var pathType = connector.getPathType();
		var pathParams = connector.getPathParams() || {};

		var ctab = this.getChemObj();

		// prepare draw parell paths
		var lineGap = pathParams.lineGap || 0;
		var lineCount = lineGap? (pathParams.lineCount || 1): 1;
		//var isEven = lineCount & 1;  // even or order number
		//if ((lineCount > 1) && lineGap)

		/*
		var coordDelta  = CU.substract(coord2, coord1);
		var w = coordDelta.x;
		var h = coordDelta.y;
		var l = CU.getDistance(coord1, coord2);
		var angleSin = h / l;
		var angleCos = w / l;

		var deltaObjVector = {'y': lineGap * angleCos, 'x': lineGap * angleSin};
		var deltaScreenCoord = CU.substract(
			this.transformCoordToContext(context, ctab, {'x': 0, 'y': 0}),
			this.transformCoordToContext(context, ctab, deltaObjVector)
		);
		var lineScreenGap = CU.getDistance(deltaScreenCoord, {'x': 0, 'y': 0});
		var totalDeltaScreenCoord = CU.multiply(deltaScreenCoord, (lineCount - 1) / 2);
		*/

		// prepare draw arrows
		var startArrowParams, endArrowParams;
		var arrowElems = [];
		if (!!pathParams.startArrowType && pathParams.startArrowWidth)
		{
			var transedLengthes = this._transformArrowLength(context, pathParams.startArrowLength, pathParams.startArrowWidth);
			startArrowParams = {
				arrowLength: transedLengthes.arrowLength,
				arrowWidth: transedLengthes.arrowWidth,
				arrowType: pathParams.startArrowType,
				arrowSide: pathParams.startArrowSide,
				drawOnCenter: (!pathParams.startArrowSide || pathParams.startArrowSide === AS.BOTH)
				//arrowOffset: pathParams.startArrowOffset
			};
		}
		if (!!pathParams.endArrowType && pathParams.endArrowWidth)
		{
			var transedLengthes = this._transformArrowLength(context, pathParams.endArrowLength, pathParams.endArrowWidth);
			var endArrowParams = {
				arrowLength: transedLengthes.arrowLength,
				arrowWidth: transedLengthes.arrowWidth,
				arrowType: pathParams.endArrowType,
				arrowSide: pathParams.endArrowSide,
				//arrowOffset: pathParams.endArrowOffset
				drawOnCenter: (!pathParams.endArrowSide || pathParams.endArrowSide === AS.BOTH)
			};
		}

		// draw parrel lines
		var drawnElems = [];
		var boundInfos = [];
		/*
		var distance = l;
		var midNo = (lineCount - 1) / 2;
		*/

		var deltaObjVector = {'y': 0, 'x': lineGap};
		var deltaScreenCoord = CU.substract(
				this.transformCoordToContext(context, ctab, {'x': 0, 'y': 0}),
				this.transformCoordToContext(context, ctab, deltaObjVector)
		);
		var lineScreenGap = CU.getDistance(deltaScreenCoord, {'x': 0, 'y': 0});

		if (pathType === PT.LINE)
		{
			var lineResult = this._doDrawLineConnectorShape(context, ctab, connector, coord1, coord2, lineCount, lineScreenGap, startArrowParams, endArrowParams, drawOptions, renderOptions);
			drawnElems = lineResult.drawnElems;
			arrowElems = lineResult.arrowElems;
			boundInfos = lineResult.boundInfos;
		}
		else if (pathType === PT.ARC)
		{
			var controlPoints = connector.getControlPoints && connector.getControlPoints();
			var controlPoint = controlPoints && controlPoints[0];
			if (controlPoint)  // can drawn only when has control point
			{
				var coordController = CU.clone(this.getTransformedCoord2D(context, controlPoint, finalTransformOptions.allowCoordBorrow));

				var lineResult = this._doDrawArcConnectorShape(context, ctab, connector, coord1, coord2, coordController, lineCount, lineScreenGap, startArrowParams, endArrowParams, drawOptions, renderOptions);
				drawnElems = lineResult.drawnElems;
				arrowElems = lineResult.arrowElems;
				boundInfos = lineResult.boundInfos;
			}
		}

		if (!boundInfos.length)
			boundInfos = null;

		if ((lineCount <= 1) && !arrowElems.length)
			return {element: drawnElems && drawnElems[0], boundInfo: boundInfos && boundInfos[0]};
		else
		{
			var drawnGroup = this.createDrawGroup(context);
			for (var i = 0; i < lineCount; ++i)
			{
				if (drawnElems[i])
					this.addToDrawGroup(drawnElems[i], drawnGroup);
			}
			for (var i = 0, l = arrowElems.length; i < l; ++i)
			{
				if (arrowElems[i])
					this.addToDrawGroup(arrowElems[i], drawnGroup);
			}
			return {element: drawnGroup, boundInfo: boundInfos};
		}
	},

	/** @private */
	_doDrawLineConnectorShape: function(context, ctab, connector, coord1, coord2, lineCount, lineGap, startArrowParams, endArrowParams, shapeDrawOptions, otherRenderOptions)
	{
		var AS = Kekule.Glyph.ArrowSide;

		var drawnElems = [];
		var boundInfos = [];
		var arrowElems = [];

		var adjustC1, adjustC2;

		var coordDelta  = CU.substract(coord2, coord1);
		var w = coordDelta.x;
		var h = coordDelta.y;
		var l = CU.getDistance(coord1, coord2);
		var angleSin = h / l;
		var angleCos = w / l;

		var distance = l;
		var midNo = (lineCount - 1) / 2;

		var lineScreenGap = lineGap;
		var deltaScreenCoord = {'y': lineScreenGap * angleCos, 'x': lineScreenGap * angleSin};
		var totalDeltaScreenCoord = CU.multiply(deltaScreenCoord, (lineCount - 1) / 2);

		var initialOffsetCoord = CU.multiply(deltaScreenCoord, (lineCount - 1) / 2);
		var c1 = CU.substract(coord1, initialOffsetCoord);
		var c2 = CU.substract(coord2, initialOffsetCoord);

		for (var i = 0; i < lineCount; ++i)
		{
			adjustC1 = null;
			adjustC2 = null;
			if (i !== 0)
			{
				c1 = CU.add(c1, deltaScreenCoord);
				c2 = CU.add(c2, deltaScreenCoord);
			}
			// consider arrow, adjust ending coord
			if (startArrowParams || endArrowParams)
			{
				var offsetIndex = i - midNo;
				var currGap = lineScreenGap * Math.abs(offsetIndex);
				//var arrowAdjustCoord = CU.multiply(deltaScreenCoord, Math.abs(i - midNo));
				// start
				if (startArrowParams)
				{
					if (startArrowParams.drawOnCenter &&
							(!startArrowParams.arrowSide
							|| (startArrowParams.arrowSide === AS.SINGLE && offsetIndex < 0)
							|| (startArrowParams.arrowSide === AS.REVERSED && offsetIndex > 0)))
					{
						var adjustLength = (startArrowParams.arrowType === Kekule.Glyph.ArrowType.OPEN) ?
						startArrowParams.arrowLength * currGap / (startArrowParams.arrowWidth / 2) :
								startArrowParams.arrowLength;
						var adjustC1 = CU.add(c1, CU.multiply(coordDelta, adjustLength / distance));
						//var adjustC1 = CU.add(c1, arrowAdjustCoord);
					}
					if (startArrowParams.drawOnCenter && i === 0)
						Kekule.ArrayUtils.pushUnique(arrowElems, this.doDrawArrowShape(context, connector, coord2, coord1, startArrowParams, shapeDrawOptions, true));
					if (!startArrowParams.drawOnCenter &&
							((startArrowParams.arrowSide === AS.SINGLE && i === 0) ||
							(startArrowParams.arrowSide === AS.REVERSED && i === lineCount - 1)))
					{
						Kekule.ArrayUtils.pushUnique(arrowElems, this.doDrawArrowShape(context, connector, c2, c1, startArrowParams, shapeDrawOptions, true));
					}
				}
				if (endArrowParams)
				{
					if (endArrowParams.drawOnCenter &&
							(!endArrowParams.arrowSide
							|| (endArrowParams.arrowSide === AS.SINGLE && offsetIndex < 0)
							|| (endArrowParams.arrowSide === AS.REVERSED && offsetIndex > 0)))
					{
						var adjustLength = (endArrowParams.arrowType === Kekule.Glyph.ArrowType.OPEN) ?
						endArrowParams.arrowLength * currGap / (endArrowParams.arrowWidth / 2) :
								endArrowParams.arrowLength;
						var adjustC2 = CU.substract(c2, CU.multiply(coordDelta, adjustLength / distance));
						//var adjustC2 = CU.substract(c2, arrowAdjustCoord);
					}

					if (endArrowParams.drawOnCenter && i === 0)
						Kekule.ArrayUtils.pushUnique(arrowElems, this.doDrawArrowShape(context, connector, coord1, coord2, endArrowParams, shapeDrawOptions));

					if (!endArrowParams.drawOnCenter &&
							((endArrowParams.arrowSide === AS.SINGLE && i === 0) ||
							(endArrowParams.arrowSide === AS.REVERSED && i === lineCount - 1)))
					{
						Kekule.ArrayUtils.pushUnique(arrowElems, this.doDrawArrowShape(context, connector, c1, c2, endArrowParams, shapeDrawOptions));
					}
				}
			}

			var	lineResult = this.doDrawLineShape(context, connector,
					adjustC1 || c1, adjustC2 || c2, shapeDrawOptions, otherRenderOptions);

			drawnElems.push(lineResult.element);
			boundInfos.push(lineResult.boundInfo);
		}

		return {'drawnElems': drawnElems, 'boundInfos': boundInfos, 'arrowElems': arrowElems};
	},

	/** @private */
	_doDrawArcConnectorShape: function(context, ctab, connector, coord1, coord2, controllerCoord, lineCount, lineGap, startArrowParams, endArrowParams, shapeDrawOptions, otherRenderOptions)
	{
		var drawnElems = [], boundInfos = [], arrowElems = [];

		// calculate out the arc center and radius, angles
		// algorithm from https://blog.csdn.net/kezunhai/article/details/39476691
		var midCoord1 = CU.divide(CU.add(controllerCoord, coord1), 2);
		var midCoord2 = CU.divide(CU.add(coord2, controllerCoord), 2);
		var k1 = -(coord1.x - controllerCoord.x) / (coord1.y - controllerCoord.y);
		var k2 = -(coord2.x - controllerCoord.x) / (coord2.y - controllerCoord.y);
		var centerCoord = {
			'x': (midCoord2.y - midCoord1.y- k2* midCoord2.x + k1*midCoord1.x)/(k1 - k2),
			'y': midCoord1.y + k1*( midCoord2.y - midCoord1.y - k2*midCoord2.x + k2*midCoord1.x)/(k1-k2)
		};
		var radius = CU.getDistance(centerCoord, coord1);
		var vector1 = CU.substract(coord1, centerCoord);
		var vector2 = CU.substract(coord2, centerCoord);
		var vectorController = CU.substract(controllerCoord, centerCoord);
		var startAngle = Kekule.GeometryUtils.standardizeAngle(Math.atan2(vector1.y, vector1.x));
		var endAngle = Kekule.GeometryUtils.standardizeAngle(Math.atan2(vector2.y, vector2.x));
		var controllerAngle = Kekule.GeometryUtils.standardizeAngle(Math.atan2(vectorController.y, vectorController.x));
		var angleDelta0 = endAngle - startAngle;
		var angleDelta1 = controllerAngle - startAngle;
		var angleDelta2 = controllerAngle - endAngle;

		var controllerSign = Math.sign(angleDelta1) * Math.sign(angleDelta2);
		var arcSign = Math.sign(angleDelta0);

		var anticlockwise = (controllerSign * arcSign) > 0;

		// get arrow directions
		var startArrowRefVector, endArrowRefVector;
		var arrowRefLength = radius + lineGap * lineCount;  // a sufficient length
		var refAngleDelta = 10 / radius;  // a moderate angle, large radius, smaller ref angle
		if (startArrowParams)
		{
			var refAngle = startAngle - (anticlockwise? refAngleDelta: -refAngleDelta);
			startArrowRefVector = {'x': Math.cos(refAngle), 'y': Math.sin(refAngle)};
		}
		if (endArrowParams)
		{
			var refAngle = endAngle + (anticlockwise? refAngleDelta: -refAngleDelta);
			endArrowRefVector = {'x': Math.cos(refAngle), 'y': Math.sin(refAngle)};
			//var refCoordDelta = {'x': radius * Math.cos(refAngle), 'y': radius * Math.sin(refAngle)};
			//endArrowRefCoord = CU.add(centerCoord, refCoordDelta);
		}

		var midNo = (lineCount - 1) / 2;
		var initialRadius = radius - lineGap * midNo;

		// draw arc
		for (var i = 0; i < lineCount; ++i)
		{
			var r = initialRadius + i * lineGap;  // radius of current child arc

			if (startArrowParams || endArrowParams && lineCount <= 1)  // TODO: now arrow can only be drawn with one child arc
			{
				if (startArrowParams)
				{
					//if (startArrowParams.drawOnCenter && i === 0)
					if (i === 0)
					{
						var arrowRefCoord = CU.add(centerCoord, CU.multiply(startArrowRefVector, r));
						Kekule.ArrayUtils.pushUnique(arrowElems, this.doDrawArrowShape(context, connector, arrowRefCoord, coord1, endArrowParams, shapeDrawOptions, true));
					}
				}
				if (endArrowParams)
				{
					//if (endArrowParams.drawOnCenter && i === 0)
					if (i === 0)
					{
						var arrowRefCoord = CU.add(centerCoord, CU.multiply(endArrowRefVector, r));
						Kekule.ArrayUtils.pushUnique(arrowElems, this.doDrawArrowShape(context, connector, arrowRefCoord, coord2, endArrowParams, shapeDrawOptions, true));
					}
				}
			}

			var pathResult = this.doDrawArcShape(context, centerCoord, r, startAngle, endAngle, anticlockwise, shapeDrawOptions);

			if (pathResult.element)
				drawnElems.push(pathResult.element);
			if (pathResult.boundInfo)
				boundInfos.push(pathResult.boundInfo);
		}

		return {'drawnElems': drawnElems, 'boundInfos': boundInfos, 'arrowElems': arrowElems};
	},

	/** @private */
	_transformArrowLength: function(context, arrowLength, arrowWidth)
	{
		var coord = {'x': arrowLength || 0, 'y': arrowWidth || 0};
		var transformed = Kekule.CoordUtils.substract(
			this.transformCoordToContext(context, this.getChemObj(), coord),
			this.transformCoordToContext(context, this.getChemObj(), {'x': 0, 'y': 0})
		);
		return {
			'arrowLength': Math.abs(transformed.x),
			'arrowWidth': Math.abs(transformed.y)
		};
	},
	/** @private */
	doDrawArrowShape: function(context, connector, coord1, coord2, arrowParams, drawOptions, reversed)
	{
		// draw an arrow at end of line (coord2)
		var AS = Kekule.Glyph.ArrowSide;
		var AT = Kekule.Glyph.ArrowType;

		var coordDelta = CU.substract(coord2, coord1);
		var distance = CU.getDistance(coord2, coord1);
		var sinAngle = coordDelta.y / distance;
		var cosAngle = coordDelta.x / distance;

		var arrowLength = arrowParams.arrowLength; // || 3;
		var arrowWidth = arrowParams.arrowWidth;  // || 6;
		var halfArrowWidth = arrowWidth / 2;

		var basePointCoord = CU.substract(coord2, CU.multiply(coordDelta, arrowLength / distance));
		var offsetCoord = {
			'x': halfArrowWidth * sinAngle,
			'y': -halfArrowWidth * cosAngle
		};
		var arrowWingEnd1;
		var arrowWingEnd2;

		var PU = Kekule.Render.DrawPathUtils;
		var pathArray = [];
		//var elems = [];
		// wingEnd1
		if (!arrowParams.arrowSide
			|| (!reversed && arrowParams.arrowSide === AS.SINGLE)
			|| (reversed && arrowParams.arrowSide === AS.REVERSED))
		{
			arrowWingEnd1 = CU.add(basePointCoord, offsetCoord);
			/*
			var elem = this.drawLine(context, coord2, arrowWingEnd1, drawOptions);
			if (elem)
				elems.push(elem);
			*/
			pathArray.push('M');
			pathArray.push([arrowWingEnd1.x, arrowWingEnd1.y]);
			pathArray.push('L');
			pathArray.push([coord2.x, coord2.y]);
		}
		else
		{
			pathArray.push('M');
			pathArray.push([coord2.x, coord2.y]);
		}
		// wingEnd2
		if (!arrowParams.arrowSide
			|| (!reversed && arrowParams.arrowSide === AS.REVERSED)
			|| (reversed && arrowParams.arrowSide === AS.SINGLE))
		{
			arrowWingEnd2 = CU.substract(basePointCoord, offsetCoord);
			/*
			elem = this.drawLine(context, coord2, arrowWingEnd2, drawOptions);
			if (elem)
				elems.push(elem);
			*/
			pathArray.push('L');
			pathArray.push([arrowWingEnd2.x, arrowWingEnd2.y]);
		}

		var doFill = false;
		// close line
		if (arrowParams.arrowType === AT.TRIANGLE)
		{
			/*
			var elem = this.drawLine(context, arrowWingEnd1 || basePointCoord, arrowWingEnd2 || basePointCoord, drawOptions);
			if (elem)
				elems.push(elem);
			*/
			var endCoord = (arrowWingEnd1 && arrowWingEnd2)? null: basePointCoord;
			if (endCoord)
			{
				pathArray.push('L');
				pathArray.push([endCoord.x, endCoord.y]);
			}
			pathArray.push('Z');
			doFill = true;
		}
		//return elems;
		var path = PU.makePath.apply(this, pathArray);
		var drawOps = Object.create(drawOptions);
		if (!doFill)
		{
			drawOps.fillColor = 'transparent';
		}
		return this.drawPath(context, path, drawOps);
	},
	/** @private */
	doDrawLineShape: function(context, connector, coord1, coord2, drawOptions, renderOptions)
	{
		var elem = this.drawLine(context, coord1, coord2, drawOptions);
		var boundInfo = this.createLineBoundInfo(coord1, coord2, drawOptions.strokeWidth);
		return {'element': elem, 'boundInfo': boundInfo};
	},
	/** @private */
	doDrawArcShape: function(context, centerCoord, radius, startAngle, endAngle, anticlockwise, drawOptions)
	{
		var overrideDrawOps = Object.create(drawOptions);
		overrideDrawOps.fillColor = null;  // do not fill, only stroke

		var elem = this.drawArc(context, centerCoord, radius, startAngle, endAngle, anticlockwise, overrideDrawOps);
		var boundInfo = this.createArcBoundInfo(centerCoord, radius, startAngle, endAngle, anticlockwise, overrideDrawOps.strokeWidth);
		return {'element': elem, 'boundInfo': boundInfo};
	}
});

/**
 * Class to render a path glyph object.
 * @class
 * @augments Kekule.Render.BaseGlyph2DRenderer
 */
Kekule.Render.PathGlyph2DRenderer = Class.create(Kekule.Render.BaseGlyph2DRenderer,
/** @lends Kekule.Render.PathGlyph2DRenderer# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Render.PathGlyph2DRenderer',
	/** @constructs */
	initialize: function($super, chemObj, drawBridge, parent)
	{
		$super(chemObj, drawBridge, parent);
		this._concreteChemObj = chemObj.getCtab();
		this._concreteRenderer = new Kekule.Render.PathGlyphCtab2DRenderer(chemObj.getCtab(), drawBridge, this);
	},
	finalize: function($super)
	{
		$super();
		if (this._concreteRenderer)
		{
			this._concreteRenderer.finalize();
			this._concreteRenderer = null;
		}
	},
	/** @ignore */
	getRenderCache: function(context)
	{
		return this._concreteRenderer.getRenderCache(context);
	},
	/** @private */
	_getConcreteRendererDrawOptions: function(options)
	{
		return options;
	},
	/** @ignore */
	isChemObjRenderedBySelf: function($super, context, obj)
	{
		var result = $super(context, obj) || (obj === this.getChemObj()) || this._concreteRenderer.isChemObjRenderedBySelf(context, obj);
		return result;
	},
	/** @ignore */
	isChemObjRenderedDirectlyBySelf: function($super, context, obj)
	{
		return $super(context, obj) || (obj === this.getChemObj());
	},
	/** @private */
	doSetRedirectContext: function($super, value)
	{
		$super(value);
		this._concreteRenderer.setRedirectContext(value);
	},
	/** @ignore */
	doDraw: function($super, context, baseCoord, options)
	{
		//console.log('dodraw path', this._concreteRenderer.getClassName());
		$super(context, baseCoord, options);

		var chemObj = this.getChemObj();
		var op = Object.create(options);
		if (op.partialDrawObjs)  // path glyph is a whole and can not be partial drawn
			op.partialDrawObjs = null;
		return this._concreteRenderer.draw(context, baseCoord, this._getConcreteRendererDrawOptions(op));
	},
	/** @ignore */
	doRedraw: function(context)
	{
		return this._concreteRenderer.redraw(context);
	},
	/** @ignore */
	doUpdate: function(context, updatedObjDetails, updateType)
	{
		var objs = this._extractObjsOfUpdateObjDetails(updatedObjDetails);

		if (objs.indexOf(this.getChemObj()) >= 0)  // root object need to be updated
		{
			var p = this.getRenderCache(context);
			this.doClear(context);
			return this.draw(context, p.baseCoord, p.options);
		}

		return this._concreteRenderer.doUpdate(context, updatedObjDetails, updateType);
	},
	/** @ignore */
	doClear: function(context)
	{
		return this._concreteRenderer.clear(context);
	},
	/** @ignore */
	estimateRenderBox: function(context, options, allowCoordBorrow)
	{
		return this._concreteRenderer.estimateRenderBox(context, this._getConcreteRendererDrawOptions(options), allowCoordBorrow);
	},
	/** @ignore */
	transformCoordToObj: function(context, chemObj, coord)
	{
		//console.log(chemObj, this.getChemObj(), chemObj === this.getChemObj());
		var obj = (this.getChemObj() === chemObj)? this._concreteChemObj: chemObj;
		return this._concreteRenderer.transformCoordToObj(context, obj, coord);
	},
	/** @ignore */
	transformCoordToContext: function(context, chemObj, coord)
	{
		//console.log(chemObj, this.getChemObj(), chemObj === this.getChemObj());
		var obj = (this.getChemObj() === chemObj)? this._concreteChemObj: chemObj;
		return this._concreteRenderer.transformCoordToContext(context, obj, coord);
	}
});
Kekule.Render.Renderer2DFactory.register(Kekule.Glyph.PathGlyph, Kekule.Render.PathGlyph2DRenderer);

})();