////////////////////////////////////////////////////////////////////////////////
//
// ADOBE SYSTEMS INCORPORATED
// Copyright 2007-2010 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file 
// in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////
package flashx.textLayout.elements
{
	import flash.display.Graphics;
	import flash.display.Shape;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.geom.Rectangle;
	import flash.text.engine.ContentElement;
	import flash.text.engine.ElementFormat;
	import flash.text.engine.FontDescription;
	import flash.text.engine.FontMetrics;
	import flash.text.engine.TextBaseline;
	import flash.text.engine.TextElement;
	import flash.text.engine.TextLine;
	import flash.text.engine.TextLineValidity;
	import flash.text.engine.TextRotation;
	import flash.text.engine.TypographicCase;
	import flash.utils.Dictionary;
	
	import flashx.textLayout.compose.FlowComposerBase;
	import flashx.textLayout.compose.IFlowComposer;
	import flashx.textLayout.compose.ISWFContext;
	import flashx.textLayout.compose.StandardFlowComposer;
	import flashx.textLayout.compose.TextFlowLine;
	import flashx.textLayout.debug.Debugging;
	import flashx.textLayout.debug.assert;
	import flashx.textLayout.events.FlowElementEventDispatcher;
	import flashx.textLayout.events.ModelChange;
	import flashx.textLayout.formats.BackgroundColor;
	import flashx.textLayout.formats.BaselineShift;
	import flashx.textLayout.formats.BlockProgression;
	import flashx.textLayout.formats.FormatValue;
	import flashx.textLayout.formats.IMEStatus;
	import flashx.textLayout.formats.ITextLayoutFormat;
	import flashx.textLayout.formats.TLFTypographicCase;
	import flashx.textLayout.formats.TextDecoration;
	import flashx.textLayout.formats.TextLayoutFormat;
	import flashx.textLayout.tlf_internal;
	import flashx.textLayout.utils.CharacterUtil;
	import flashx.textLayout.utils.LocaleUtil;
	
	use namespace tlf_internal;
		
	
	/** Base class for FlowElements that appear at the lowest level of the flow hierarchy. FlowLeafElement objects have
	* no children and include InlineGraphicElement objects and SpanElement objects.
	*
	* @playerversion Flash 10
	* @playerversion AIR 1.5
	* @langversion 3.0
	*
	* @see InlineGraphicElement
	* @see SpanElement
	*/
	public class FlowLeafElement extends FlowElement
	{				
		/** Holds the content of the leaf @private */
		protected var _blockElement:ContentElement;
		
		/** @private
		 * Holds the text for the leaf element - unless there's a valid blockElement, 
		 * in which case the text is in the rawText field of the blockElement.
		 */
		protected var _text:String;	// holds the text property if the blockElement is null
		private var _hasAttachedListeners:Boolean;	// true if FTE eventMirror may be in use
		
		/** @private The event dispatcher that acts as an event mirror */
		tlf_internal var _eventMirror:FlowElementEventDispatcher = null;
		
		/** 
		 * Base class - invoking new FlowLeafElement() throws an error exception. 
		 *
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
		 * @langversion 3.0
		 * 
		 */
		public function FlowLeafElement()
		{
			_hasAttachedListeners = false;
			super();
		}
		
		/** @private */
		override tlf_internal function createContentElement():void
		{
			CONFIG::debug { assert(_blockElement != null, "_blockElement not allocated in derived class"); }
			if (_computedFormat)
			{
				_blockElement.elementFormat = computeElementFormat();
				CONFIG::debug { Debugging.traceFTEAssign(_blockElement,"elementFormat",_blockElement.elementFormat); }
			}
			if (parent)
				parent.insertBlockElement(this,_blockElement);
		}
		/** @private */
		override tlf_internal function releaseContentElement():void
		{
			_blockElement = null;
			_computedFormat = null;
		}
		
		private function blockElementExists():Boolean
		{
			return _blockElement != null;
		}

		/** @private */
		tlf_internal function getBlockElement():ContentElement
		{ 
			if (!_blockElement)
				createContentElement();
			return _blockElement; 
		}
		
		/** @private
		 * Gets the EventDispatcher associated with this FlowElement.  Use the functions
		 * of EventDispatcher such as <code>setEventHandler()</code> and <code>removeEventHandler()</code> 
		 * to capture events that happen over this FlowLeafElement object.  The
		 * event handler that you specify will be called after this FlowElement object does
		 * the processing it needs to do.
		 * 
		 * Note that the event dispatcher will only dispatch FlowElementMouseEvent events.
		 *
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
		 * @langversion 3.0
		 *
		 * @see flash.events.EventDispatcher
		 * @see flashx.textLayout.events.FlowElementMouseEvent
		 */
		tlf_internal override function getEventMirror():IEventDispatcher
		{
			if (!_eventMirror)
				_eventMirror = new FlowElementEventDispatcher(this);
			return _eventMirror;
		}
		
		/** @private
		 * Checks whether an event dispatcher is attached, and if so, if the event dispatcher
		 * has any active listeners.
		 */
		tlf_internal override function hasActiveEventMirror():Boolean
		{
			return _eventMirror && (_eventMirror._listenerCount != 0);
		}
		
		/** @private This is done so that the TextContainerManager can discover EventMirrors in a TextFlow. */
		tlf_internal override function appendElementsForDelayedUpdate(tf:TextFlow,changeType:String):void
		{ 
			if (changeType == ModelChange.ELEMENT_ADDED)
			{
				if (this.hasActiveEventMirror())
					tf.incInteractiveObjectCount();
			}
			else if (changeType == ModelChange.ELEMENT_REMOVAL)
			{
				if (this.hasActiveEventMirror())
					tf.decInteractiveObjectCount();
			}
			super.appendElementsForDelayedUpdate(tf,changeType);
		}
		
		/**
		 * The text associated with the FlowLeafElement:
		 * <p><ul>
		 * <li>The value for SpanElement subclass will be one character less than <code>textLength</code> if this is the last span in a ParagraphELement.</li>
		 * <li>The value for BreakElement subclass is a U+2028</li>
		 * <li>The value for TabElement subclass is a tab</li>
		 * <li>The value for InlineGraphicElement subclass is U+FDEF</li>
		 * </ul></p>
		 *
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
	 	 * @langversion 3.0
	 	 *
	 	 * @see flashx.textLayout.elements.SpanElement#replaceText()
		 */
		public function get text():String
		{
			return _text;
		}
		
		/** @private */
		tlf_internal function getElementFormat():ElementFormat
		{ 			
			if (!_blockElement)
				createContentElement();
			return _blockElement.elementFormat; 
 		}
		
		/** @private */
		tlf_internal override function setParentAndRelativeStart(newParent:FlowGroupElement,newStart:int):void
		{
			if (newParent == parent)
				return;
		
			var hasBlock:Boolean = _blockElement != null;
			
			if (_blockElement && parent && parent.hasBlockElement())	// remove textElement from the parent content
				parent.removeBlockElement(this,_blockElement);
			if (newParent && !newParent.hasBlockElement() && _blockElement)
				newParent.createContentElement();
					
			super.setParentAndRelativeStart(newParent,newStart);
			
			// Update the FTE ContentElement structure. If the parent has FTE elements, then create FTE elements for the leaf node 
			// if it doesn't already have them, and add them in. If the parent does not have FTE elements, release the leaf's FTE
			// elements also so they match.
			if (parent)
			{
				if (parent.hasBlockElement())
				{
					if (!_blockElement)
						createContentElement();
					else if (hasBlock)	// don't do this if the _blockElement was constructed as side-effect of setParentAndRelativeStart; in that case, it's already attached
						parent.insertBlockElement(this,_blockElement);
				}
				else if (_blockElement)
					releaseContentElement();
			}
		}
	
		/** @private Only used by SpanElement.splitAtPosition */
		tlf_internal function quickInitializeForSplit(sibling:FlowLeafElement,newSpanLength:int,newSpanTextElement:TextElement):void
		{
			setTextLength(newSpanLength);
			_blockElement = newSpanTextElement;
			if (_blockElement)
				_text = _blockElement.text;
			quickCloneTextLayoutFormat(sibling);
			var tf:TextFlow = sibling.getTextFlow();
			if (tf == null || tf.formatResolver == null)
			{
				_computedFormat = sibling._computedFormat;
				if (_blockElement)
					_blockElement.elementFormat = sibling.getElementFormat();
			}
		}
		
		/**
		 * Returns the next FlowLeafElement object.  
		 * 
		 * @param limitElement	Specifies FlowGroupElement on whose last leaf to stop looking. A value of null (default) 
		 * 	means search till no more elements.
		 * @return 	next FlowLeafElement or null if at the end
		 *
		 * @includeExample examples\FlowLeafElement_getNextLeafExample.as -noswf
		 *
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
	 	 * @langversion 3.0
	 	 *
		 */
		 
		 public function getNextLeaf(limitElement:FlowGroupElement=null):FlowLeafElement
		{
			if (!parent)
				return null;
			return parent.getNextLeafHelper(limitElement,this);
		}
		
		/**
		 * Returns the previous FlowLeafElement object.  
		 * 
		 * @param limitElement	Specifies the FlowGroupElement on whose first leaf to stop looking.   null (default) means search till no more elements.
		 * @return 	previous leafElement or null if at the end
		 *
		 * @includeExample examples\FlowLeafElement_getPreviousLeafExample.as -noswf
		 *
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
	 	 * @langversion 3.0
	 	 *
		 */
		 
		public function getPreviousLeaf(limitElement:FlowGroupElement=null):FlowLeafElement
		{
			if (!parent)
				return null;
			return parent.getPreviousLeafHelper(limitElement,this);
		}
		
		/** @private */
		public override function getCharAtPosition(relativePosition:int):String
		{
			return _text ?  _text.charAt(relativePosition) : "";
		} 
		
		/** @private */
		tlf_internal override function normalizeRange(normalizeStart:uint,normalizeEnd:uint):void
		{
			// this does the cascade - potential optimization to skip it if the _blockElement isn't attached
			if (_blockElement)
				computedFormat;
		}
		
		/** Returns the FontMetrics object for the span. The properties of the FontMetrics object describe the 
		 * emBox, strikethrough position, strikethrough thickness, underline position, 
		 * and underline thickness for the specified font. 
		 *
 		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
	 	 * @langversion 3.0
	 	 *
		 * @see flash.text.engine.FontMetrics
		 * @see flash.text.engine.ElementFormat#getFontMetrics()
		 *
		 * @return font metrics associated with the span
		 */
		public function getComputedFontMetrics():FontMetrics
		{
			if (!_blockElement)
				createContentElement();
			var ef:ElementFormat = _blockElement.elementFormat;
			if (!ef)
				return null;
				
			var tf:TextFlow = getTextFlow()
			if (tf && tf.flowComposer && tf.flowComposer.swfContext)
				return tf.flowComposer.swfContext.callInContext(ef.getFontMetrics,ef,null,true);

			return ef.getFontMetrics();
		}
		
		/** @private */
		tlf_internal static function resolveDomBaseline(computedFormat:ITextLayoutFormat, para:ParagraphElement):String
		{
			CONFIG::debug { assert(computedFormat != null,"bad call to resolveDomBaseline"); }
			
			var domBase:String = computedFormat.dominantBaseline;
			if(domBase == FormatValue.AUTO)
			{
				if(computedFormat.textRotation == TextRotation.ROTATE_270 /*|| 
					this.computedFormat.blockProgression == BlockProgression.RL*/)
					domBase = TextBaseline.IDEOGRAPHIC_CENTER;
				else
				{
					//otherwise, avoid using the locale of the element and use the paragraph's locale
					if(para != null)
						domBase = para.getEffectiveDominantBaseline();
					else
						domBase = LocaleUtil.dominantBaseline(computedFormat.locale);
				}
			}
			
			return domBase;
		}
		
		/** @private */
		tlf_internal function computeElementFormat():ElementFormat
		{
			CONFIG::debug { assert(_computedFormat != null,"bad call to computeElementFormat"); }

			var tf:TextFlow = getTextFlow();
			return computeElementFormatHelper (_computedFormat, getParagraph(), tf && tf.flowComposer ? tf.flowComposer.swfContext : null);
		}
		
		/** @private */
		static tlf_internal function computeElementFormatHelper(computedFormat:ITextLayoutFormat, para:ParagraphElement, swfContext:ISWFContext):ElementFormat
		{
			// compute the cascaded elementFormat
			var format:ElementFormat = new ElementFormat();
			CONFIG::debug { Debugging.traceFTECall(format,null,"new ElementFormat()"); }
			
			format.alignmentBaseline	= computedFormat.alignmentBaseline;
			format.alpha				= Number(computedFormat.textAlpha);
			format.breakOpportunity		= computedFormat.breakOpportunity;
			format.color				= uint(computedFormat.color);
			format.dominantBaseline		= resolveDomBaseline(computedFormat, para);
			
			format.digitCase			= computedFormat.digitCase;
			format.digitWidth			= computedFormat.digitWidth;
			format.ligatureLevel		= computedFormat.ligatureLevel;
			format.fontSize				= Number(computedFormat.fontSize);
			format.kerning				= computedFormat.kerning;
			format.locale				= computedFormat.locale;
			format.trackingLeft			= TextLayoutFormat.trackingLeftProperty.computeActualPropertyValue(computedFormat.trackingLeft,format.fontSize);
			format.trackingRight		= TextLayoutFormat.trackingRightProperty.computeActualPropertyValue(computedFormat.trackingRight,format.fontSize);
			format.textRotation			= computedFormat.textRotation;
			format.baselineShift 		= -(TextLayoutFormat.baselineShiftProperty.computeActualPropertyValue(computedFormat.baselineShift, format.fontSize));
			switch (computedFormat.typographicCase)
			{
				case TLFTypographicCase.LOWERCASE_TO_SMALL_CAPS:
					format.typographicCase = TypographicCase.CAPS_AND_SMALL_CAPS;
					break;
				case TLFTypographicCase.CAPS_TO_SMALL_CAPS:
					format.typographicCase = TypographicCase.SMALL_CAPS;
					break;
				/* Others map directly so handle it in the default case */
				default:
					format.typographicCase = computedFormat.typographicCase;
					break;
			}
			
			CONFIG::debug { Debugging.traceFTEAssign(format,"alignmentBaseline",format.alignmentBaseline); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"alpha",format.alpha); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"breakOpportunity",format.breakOpportunity); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"color",format.color); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"dominantBaseline",format.dominantBaseline); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"digitCase",format.digitCase); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"digitWidth",format.digitWidth); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"ligatureLevel",format.ligatureLevel); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"fontSize",format.fontSize); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"kerning",format.kerning); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"locale",format.locale); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"trackingLeft",format.trackingLeft); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"trackingRight",format.trackingRight); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"typographicCase",format.typographicCase); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"textRotation",format.textRotation); }
			CONFIG::debug { Debugging.traceFTEAssign(format,"baselineShift",format.baselineShift);	 }	
			
			// set the fontDesription in the cascadedFormat
			var fd:FontDescription = new FontDescription();
			fd.fontWeight = computedFormat.fontWeight;
			fd.fontPosture = computedFormat.fontStyle;
			fd.fontName = computedFormat.fontFamily;
			fd.renderingMode = computedFormat.renderingMode;
			fd.cffHinting = computedFormat.cffHinting;
			
			// the fontLookup may be override by the resolveFontLookupFunction
			if (GlobalSettings.resolveFontLookupFunction == null)
				fd.fontLookup = computedFormat.fontLookup;
			else
			{
				fd.fontLookup = GlobalSettings.resolveFontLookupFunction(swfContext ? FlowComposerBase.computeBaseSWFContext(swfContext) : null, computedFormat);
			}
			// and now give the fontMapper a shot at rewriting the FontDescription
			var fontMapper:Function = GlobalSettings.fontMapperFunction;
			if (fontMapper != null)
				fontMapper(fd);
			CONFIG::debug { Debugging.traceFTECall(fd,null,"new FontDescription()"); }
			CONFIG::debug { Debugging.traceFTEAssign(fd,"fontWeight",fd.fontWeight);	 }
			CONFIG::debug { Debugging.traceFTEAssign(fd,"fontPosture",fd.fontPosture);	 }
			CONFIG::debug { Debugging.traceFTEAssign(fd,"fontName",fd.fontName);	 }
			CONFIG::debug { Debugging.traceFTEAssign(fd,"renderingMode",fd.renderingMode);	 }
			CONFIG::debug { Debugging.traceFTEAssign(fd,"cffHinting",fd.cffHinting);	 }
			CONFIG::debug { Debugging.traceFTEAssign(fd,"fontLookup",fd.fontLookup);	 }
			
			format.fontDescription = fd;
			CONFIG::debug { Debugging.traceFTEAssign(format,"fontDescription",fd); }
			
			//Moved code here because original code tried to access fontMetrics prior to setting the elementFormat.
			//Since getFontMetrics returns the value of blockElement.elementFormat.getFontMetrics(), we cannot call this
			//until after the element has been set. Watson 1820571 - gak 06.11.08
			// Adjust format for superscript/subscript
			if (computedFormat.baselineShift == BaselineShift.SUPERSCRIPT || 
				computedFormat.baselineShift == BaselineShift.SUBSCRIPT)
			{
				var fontMetrics:FontMetrics;
				if (swfContext)
					fontMetrics = swfContext.callInContext(format.getFontMetrics,format,null,true);
				else	
					fontMetrics = format.getFontMetrics();	
				if (computedFormat.baselineShift == BaselineShift.SUPERSCRIPT)
				{
					format.baselineShift = (fontMetrics.superscriptOffset * format.fontSize);
					format.fontSize = fontMetrics.superscriptScale * format.fontSize;
				}
				else // it's subscript
				{
					format.baselineShift = (fontMetrics.subscriptOffset * format.fontSize);
					format.fontSize = fontMetrics.subscriptScale * format.fontSize;
				}
				CONFIG::debug { Debugging.traceFTEAssign(format,"baselineShift",format.baselineShift); }
				CONFIG::debug { Debugging.traceFTEAssign(format,"fontSize",format.fontSize); }
			}			
			return format;
		}

		/** 
		 * The computed text format attributes that are in effect for this element.
		 * Takes into account the inheritance of attributes.
		 *
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
	 	 * @langversion 3.0
	 	 *
		 * @see flashx.textLayout.formats.ITextLayoutFormat
		 */
		public override function get computedFormat():ITextLayoutFormat
		{		
			if (!_computedFormat)
			{
				_computedFormat = doComputeTextLayoutFormat();

				if (_blockElement)
				{
					_blockElement.elementFormat = computeElementFormat();
					CONFIG::debug { Debugging.traceFTEAssign(_blockElement,"elementFormat",_blockElement.elementFormat); }
				}

			}
			return _computedFormat;
		}
		
		/** Returns the calculated lineHeight from this element's properties.  @private */
		tlf_internal function getEffectiveLineHeight(blockProgression:String):Number
		{
			//ignore the leading on a TCY Block. If the element is in a TCYBlock, it has no leading
			if (blockProgression == BlockProgression.RL && (parent is TCYElement))
				return 0;
			CONFIG::debug { assert(_computedFormat != null,"Missing _computedFormat in FlowLeafElement.getEffectiveLineHeight"); }
			return TextLayoutFormat.lineHeightProperty.computeActualPropertyValue(_computedFormat.lineHeight, getEffectiveFontSize());
		}
		
		/** @private 
		 * Get the "inline box" for the element as defined by the CSS visual formatting model (http://www.w3.org/TR/CSS2/visuren.html)
		 * @param	blockProgression	Block progression
		 * @param	textLine			The containing text line
		 * @param	para				The containing para. Only used for resolving AUTO dominantBaseline value. 
		 * 								May be null, in which case the AUTO dominantBaseline value is resolved based on other attributes (such as the element's computed locale). 	
		 * @param	swfContext			The SWF context in which certain method calls (such as the one used to get font metrics) are made
		 * 								May be null in which case the current SWF context is used.
		 * @return 						Null if the element is not "inline"
		 * 								Otherwise, a rectangle representing the inline box. Top and Bottom are relative to the line's Roman baseline. Left and Right are ignored.
		 */
		tlf_internal function getCSSInlineBox(blockProgression:String, textLine:TextLine, para:ParagraphElement=null, swfContext:ISWFContext=null):Rectangle
		{
			// TODO-9/27/10: TCYs typically don't affect leading, but this may not be appropriate for LeadingModel.BOX 
			if (blockProgression == BlockProgression.RL && (parent is TCYElement))
				return null;
			
			return getCSSInlineBoxHelper (computedFormat, getComputedFontMetrics(), textLine, para);
		}
		
		/** @private 
		 * Get the "inline box" for an element with the specified computed format as defined by the CSS visual formatting model (http://www.w3.org/TR/CSS2/visuren.html)
		 * For a span, leading is applied equally above and below the em-box such that the box's height equals lineHeight. 
		 * Alignment relative to the baseline (using baselineShift, dominantBaseline, alignmentBaseline) is taken into account.
		 * @param	textLine		The containing text line
		 * @param	para			The containing para. Only used for resolving AUTO dominantBaseline value. 
		 * 							May be null, in which case the AUTO dominantBaseline value is resolved based on other attributes (such as the element's computed locale). 	
		 * @return 	A rectangle representing the inline box. Top and Bottom are relative to the line's Roman baseline. Left and Right are ignored.
		 */
		static tlf_internal function getCSSInlineBoxHelper(computedFormat:ITextLayoutFormat, metrics:FontMetrics, textLine:TextLine, para:ParagraphElement=null):Rectangle
		{
			var emBox:Rectangle 	= metrics.emBox;
			
			var ascent:Number 		= -emBox.top;
			var descent:Number 		= emBox.bottom;
			var textHeight:Number	= emBox.height;
			
			var fontSize:Number 	= computedFormat.fontSize;
			var lineHeight:Number 	= TextLayoutFormat.lineHeightProperty.computeActualPropertyValue(computedFormat.lineHeight, fontSize);
			var halfLeading:Number	= (lineHeight - textHeight) / 2;
			
			// Apply half leading equally above and below the em box
			emBox.top -= halfLeading;
			emBox.bottom += halfLeading;
			
			// Account for the effect of dominantBaseline
			var computedDominantBaseline:String = resolveDomBaseline(computedFormat, para);
			switch (computedDominantBaseline)
			{
				case TextBaseline.ASCENT:
				case TextBaseline.IDEOGRAPHIC_TOP:
					emBox.offset(0, ascent); 
					break;
				
				case TextBaseline.IDEOGRAPHIC_CENTER:
					emBox.offset(0, ascent - textHeight/2);
					break;
				
				case TextBaseline.ROMAN:
					break;
				
				case TextBaseline.DESCENT:
				case TextBaseline.IDEOGRAPHIC_BOTTOM:
					emBox.offset(0, -descent);
			}
			
			// Account for the effect of alignmentBaseline
			var computedAlignmentBaseline:String = (computedFormat.alignmentBaseline == TextBaseline.USE_DOMINANT_BASELINE ? computedDominantBaseline : computedFormat.alignmentBaseline);
			emBox.offset(0, textLine.getBaselinePosition(computedAlignmentBaseline));
			
			// Account for the effect of baselineShift
			var baselineShift:Number;
			if (computedFormat.baselineShift == BaselineShift.SUPERSCRIPT)
				baselineShift = metrics.superscriptOffset * fontSize;
			else if (computedFormat.baselineShift == BaselineShift.SUBSCRIPT)
				baselineShift = metrics.subscriptOffset * fontSize;
			else
				baselineShift = -computedFormat.baselineShift;
			
			emBox.offset(0, baselineShift);
			
			return emBox;
		}
		
		/** Returns the fontSize from this element's properties.  @private */
		tlf_internal function getEffectiveFontSize():Number
		{
			return Number(computedFormat.fontSize);
		}
		
		/** @private */
		tlf_internal function getSpanBoundsOnLine(textLine:TextLine, blockProgression:String):Array
		{
			var line:TextFlowLine = TextFlowLine(textLine.userData);
			var paraStart:int = line.paragraph.getAbsoluteStart();
			var lineEnd:int = (line.absoluteStart + line.textLength) - paraStart;
			var spanStart:int = getAbsoluteStart() - paraStart;		// get start pos relative to the paragraph (which might not be the parent)
			var endPos:int = spanStart + text.length;		// so we don't include the paragraph terminator character, if present
		
			// Clip to start of line	
			var startPos:int = Math.max(spanStart, line.absoluteStart - paraStart);
			
			// Clip to end of line	
			// Heuristic for detecting spaces at the end of the line and eliminating them from the range so they won't be underlined.
			if (endPos >= lineEnd)
			{
				endPos = lineEnd;
				var spanText:String = text;
				while (endPos > startPos && CharacterUtil.isWhitespace(spanText.charCodeAt(endPos - spanStart - 1)))
					--endPos;
			}

			var mainRects:Array = [];
			line.calculateSelectionBounds(textLine, mainRects, startPos, endPos, blockProgression, [ line.textHeight,0]);
			return mainRects;
		}
		
		/** @private */
		tlf_internal function updateIMEAdornments(tLine:TextLine, blockProgression:String, imeStatus:String):void
		{
			var metrics:FontMetrics = getComputedFontMetrics();
			var spanBoundsArray:Array = getSpanBoundsOnLine(tLine, blockProgression);
			//this is pretty much always going to have a length of 1, but just to be sure...
			for (var i:int = 0; i < spanBoundsArray.length; i++)
			{
				//setup ime variables
				var imeLineThickness:int = 1;
				var imeLineColor:uint = 0x000000;
				var imeLineStartX:Number = 0;
				var imeLineStartY:Number = 0;
				var imeLineEndX:Number = 0;
				var imeLineEndY:Number = 0;
				
				//selected text draws with 2 px
				if(imeStatus == IMEStatus.SELECTED_CONVERTED || imeStatus == IMEStatus.SELECTED_RAW)
				{
					imeLineThickness = 2;
				}
				//Raw or deadkey text draws with grey
				if(imeStatus == IMEStatus.SELECTED_RAW || imeStatus == IMEStatus.NOT_SELECTED_RAW
					|| imeStatus == IMEStatus.DEAD_KEY_INPUT_STATE)
				{
					imeLineColor = 0xA6A6A6;
				}
				
				var spanBounds:Rectangle = spanBoundsArray[i] as Rectangle;
				var stOffset:Number = calculateStrikeThrough(tLine, blockProgression, metrics);
				var ulOffset:Number = calculateUnderlineOffset(stOffset, blockProgression, metrics, tLine);
				
				if (blockProgression != BlockProgression.RL)
				{
					imeLineStartX = spanBounds.topLeft.x + 1;
					imeLineEndX = spanBounds.topLeft.x + spanBounds.width - 1;
					imeLineStartY = ulOffset;
					imeLineEndY = ulOffset;
				}
				else
				{
					//is this TCY?
					var line:TextFlowLine = tLine.userData as TextFlowLine;
					var elemIdx:int = this.getAbsoluteStart() - line.absoluteStart;
					imeLineStartY = spanBounds.topLeft.y + 1;
					imeLineEndY = spanBounds.topLeft.y + spanBounds.height - 1;
					
					//elemIdx can sometimes be negative if the text is being wrapped due to a
					//resize gesture - in which case the tLine has not necessarily been updated.
					//If the elemIdx is invalid, just treat it like it's normal ttb text - gak 07.08.08
					if(elemIdx < 0 || tLine.atomCount <= elemIdx || tLine.getAtomTextRotation(elemIdx) != TextRotation.ROTATE_0)
					{
						imeLineStartX = ulOffset;
						imeLineEndX = ulOffset;
					}
					else
					{
						//it is TCY!
						var tcyParent:TCYElement =  this.getParentByType(TCYElement) as TCYElement;
						CONFIG::debug{ assert(tcyParent != null, "What kind of object is this that is ROTATE_0, but not TCY?");}
						
						//only perform calculations for TCY adornments when we are on the last leaf.  ONLY the last leaf matters
						if((this.getAbsoluteStart() + this.textLength) == (tcyParent.getAbsoluteStart() + tcyParent.textLength))
						{
							var tcyAdornBounds:Rectangle = new Rectangle();	// NO PMD
							tcyParent.calculateAdornmentBounds(tcyParent, tLine, blockProgression, tcyAdornBounds);
							var baseULAdjustment:Number = metrics.underlineOffset + (metrics.underlineThickness/2);
							
							imeLineStartY = tcyAdornBounds.top + 1;
							imeLineEndY = tcyAdornBounds.bottom - 1;
							imeLineStartX = spanBounds.bottomRight.x + baseULAdjustment;
							imeLineEndX = spanBounds.bottomRight.x + baseULAdjustment;
						}
					}
				}
				
				//Build the shape
				var selObj:Shape = new Shape();	// NO PMD
				//TODO - this is probably going to need to be overridable in the full implementation
				selObj.alpha = 1;       				
				selObj.graphics.beginFill(imeLineColor);
				
				selObj.graphics.lineStyle(imeLineThickness, imeLineColor, selObj.alpha);
				selObj.graphics.moveTo(imeLineStartX, imeLineStartY);
				selObj.graphics.lineTo(imeLineEndX, imeLineEndY);
				
				selObj.graphics.endFill();
				tLine.addChild(selObj);
			}
		}
		
		
		/** @private return number of shapes added */
		tlf_internal function updateAdornments(tLine:TextLine, blockProgression:String):int
		{
			CONFIG::debug { assert(_computedFormat != null,"invalid call to updateAdornments"); }

			// Only work on lines with strikethrough or underline
			if (_computedFormat.textDecoration == TextDecoration.UNDERLINE || _computedFormat.lineThrough || _computedFormat.backgroundAlpha > 0 && _computedFormat.backgroundColor != BackgroundColor.TRANSPARENT)
			{
				var spanBoundsArray:Array = getSpanBoundsOnLine(tLine, blockProgression);
				for (var i:int = 0; i < spanBoundsArray.length; i++)
					updateAdornmentsOnBounds(tLine, blockProgression, spanBoundsArray[i]);
				return spanBoundsArray.length ;
			}
			return 0;
		}
		 
		private function updateAdornmentsOnBounds(tLine:TextLine, blockProgression:String, spanBounds:Rectangle):void
		{
			CONFIG::debug { assert(_computedFormat != null,"invalid call to updateAdornmentsOnBounds"); }

			var metrics:FontMetrics = getComputedFontMetrics();


			var backgroundOnly:Boolean = !(_computedFormat.textDecoration == TextDecoration.UNDERLINE || _computedFormat.lineThrough);
				
			if (!backgroundOnly)
			{
				var shape:Shape;
				var g:Graphics;
				shape = new Shape();
				shape.alpha = Number(_computedFormat.textAlpha);
				g = shape.graphics;
						
				var stOffset:Number = calculateStrikeThrough(tLine, blockProgression, metrics);
				var ulOffset:Number = calculateUnderlineOffset(stOffset, blockProgression, metrics, tLine);
			}
						
			if (blockProgression != BlockProgression.RL)
			{
				addBackgroundRect (tLine, metrics, spanBounds, true); 

				if (_computedFormat.textDecoration == TextDecoration.UNDERLINE)
				{
					g.lineStyle(metrics.underlineThickness, _computedFormat.color as uint);
					g.moveTo(spanBounds.topLeft.x, ulOffset);
					g.lineTo(spanBounds.topLeft.x + spanBounds.width, ulOffset);
				}
				
				if((_computedFormat.lineThrough))
				{
					g.lineStyle(metrics.strikethroughThickness, _computedFormat.color as uint);
					g.moveTo(spanBounds.topLeft.x, stOffset);
					g.lineTo(spanBounds.topLeft.x + spanBounds.width, stOffset);
				}
			}
			else
			{
				//is this TCY?
				var line:TextFlowLine = tLine.userData as TextFlowLine;
				var elemIdx:int = this.getAbsoluteStart() - line.absoluteStart;
				
				//elemIdx can sometimes be negative if the text is being wrapped due to a
				//resize gesture - in which case the tLine has not necessarily been updated.
				//If the elemIdx is invalid, just treat it like it's normal ttb text - gak 07.08.08
				if (elemIdx < 0 || tLine.atomCount <= elemIdx || tLine.getAtomTextRotation(elemIdx) != TextRotation.ROTATE_0)
				{
					addBackgroundRect (tLine, metrics, spanBounds, false);

					if (_computedFormat.textDecoration == TextDecoration.UNDERLINE)
					{
						g.lineStyle(metrics.underlineThickness, _computedFormat.color as uint);
						g.moveTo(ulOffset, spanBounds.topLeft.y);
						g.lineTo(ulOffset, spanBounds.topLeft.y + spanBounds.height);
					}
					
					if (_computedFormat.lineThrough == true)
					{
						g.lineStyle(metrics.strikethroughThickness, _computedFormat.color as uint);
						g.moveTo(-stOffset, spanBounds.topLeft.y);
						g.lineTo(-stOffset, spanBounds.topLeft.y + spanBounds.height);															
					}
				}
				else
				{
					addBackgroundRect (tLine, metrics, spanBounds, true, true); 
					
					if (!backgroundOnly)
					{
						//it is TCY!
						var tcyParent:TCYElement =  this.getParentByType(TCYElement) as TCYElement;
						CONFIG::debug{ assert(tcyParent != null, "What kind of object is this that is ROTATE_0, but not TCY?");}
						
						//if the locale of the paragraph is Chinese, we need to adorn along the left and not right side.
						var tcyPara:ParagraphElement = this.getParentByType(ParagraphElement) as ParagraphElement;
						var lowerLocale:String = tcyPara.computedFormat.locale.toLowerCase();
						var adornRight:Boolean = (lowerLocale.indexOf("zh") != 0);
						
						
						//only perform calculations for TCY adornments when we are on the last leaf.  ONLY the last leaf matters
						if((this.getAbsoluteStart() + this.textLength) == (tcyParent.getAbsoluteStart() + tcyParent.textLength))
						{
							var tcyAdornBounds:Rectangle = new Rectangle();
							tcyParent.calculateAdornmentBounds(tcyParent, tLine, blockProgression, tcyAdornBounds);
							
							if (_computedFormat.textDecoration == TextDecoration.UNDERLINE)
							{
								g.lineStyle(metrics.underlineThickness, _computedFormat.color as uint);
								var baseULAdjustment:Number = metrics.underlineOffset + (metrics.underlineThickness/2);
								var xCoor:Number = adornRight ? spanBounds.right : spanBounds.left;
								if(!adornRight)
									baseULAdjustment = -baseULAdjustment;
								
								g.moveTo(xCoor + baseULAdjustment, tcyAdornBounds.top);
								g.lineTo(xCoor + baseULAdjustment, tcyAdornBounds.bottom);
							}
	
							if (_computedFormat.lineThrough == true)
							{
								var tcyMid:Number = spanBounds.bottomRight.x - tcyAdornBounds.x;
								tcyMid /= 2;
								tcyMid += tcyAdornBounds.x;
								
								g.lineStyle(metrics.strikethroughThickness, _computedFormat.color as uint);
								g.moveTo(tcyMid, tcyAdornBounds.top);
								g.lineTo(tcyMid, tcyAdornBounds.bottom);
							}
						}
					}
				}
			}
			
		 	// Add the shape as a child of the TextLine. In some cases, the textLine we passed through may not be the same one that's
			// in the TextFlowLine textLineCache. This can happen if the TextFlowLine's textLine is invalid and a child of the 
			// container. In that case, we generated a valid TextLine and passed it in as tLine so that we have correct metrics 
			// for generating the adornment shapes.
			if (shape)
			{
	 			var peekLine:TextLine = (tLine.userData as TextFlowLine).peekTextLine();
				CONFIG::debug{ assert(peekLine == null || peekLine == tLine || peekLine.validity == TextLineValidity.INVALID, "Valid/invalid mismatch"); }
				// CONFIG::debug{ assert(peekLine == null || peekLine == TextFlowLine(tLine.userData).getTextLine(false), "Valid/invalid mismatch"); }
				if (peekLine && tLine != peekLine)
				{
					// belief is that this line of code is never hit
					CONFIG::debug { assert(false,"updateAdornmentsOnBounds: peekLine doesn't match textLine"); }
					tLine = peekLine;
				}
				tLine.addChild(shape);
			}
		}
		
		/** @private
		 * Adds the background rectangle (if needed), making adjustments for glyph shifting as appropriate
		 */
		 private function addBackgroundRect(tLine:TextLine, metrics:FontMetrics, spanBounds:Rectangle, horizontalText:Boolean, isTCY:Boolean=false):void
		 {
			if(_computedFormat.backgroundAlpha == 0 || _computedFormat.backgroundColor == BackgroundColor.TRANSPARENT)
				return;
				
			var tf:TextFlow = this.getTextFlow();
			// ensure the TextFlow has a background manager - but its only supported with the StandardFlowComposer at this time			
			if (!tf.getBackgroundManager())
				return;
					
			// The background rectangle usually needs to coincide with the passsed-in span bounds.
			var r:Rectangle = spanBounds.clone();
			
			// With constrained glyph shifting (such as when superscript/subscript are in use), we'd like the
			// background rectangle to follow the glyphs. Not so for arbitrary glyph shifting (such as when 
			// baseline shift or baseline alignment are in use)	 	
			// TODO-06/12/2009: Need to figure out adjustment for TCY background rect. No adjustment for now.
			if (!isTCY && (_computedFormat.baselineShift == BaselineShift.SUPERSCRIPT || _computedFormat.baselineShift == BaselineShift.SUBSCRIPT))
			{	
				// The atom bounds returned by FTE do not reflect the effect of glyph shifting.
				// We approximate this effect by making the following assumptions (strikethrough/underline code does the same)
				// - The strike-through adornment runs through the center of the glyph
				// - The Roman baseline is halfway between the center and bottom (descent)
				// Effectively, the glyph's descent equals the strike-through offset, and its ascent is three times that
				
				var desiredExtent:Number; // The desired extent of the rectangle in the block progression direction
				var baselineShift:Number; 
				var fontSize:Number = getEffectiveFontSize();
				var baseStrikethroughOffset:Number = metrics.strikethroughOffset + metrics.strikethroughThickness/2;
				
				if (_computedFormat.baselineShift == BaselineShift.SUPERSCRIPT)
				{
					// The rectangle needs to sit on the line's descent and must extend far enough to accommodate the
					// ascender of the glyph (that has moved up because of superscript)
					
					var glyphAscent:Number = -3 * baseStrikethroughOffset; // see assumptions above
					baselineShift = -metrics.superscriptOffset * fontSize;
					var lineDescent:Number = tLine.getBaselinePosition(TextBaseline.DESCENT) - tLine.getBaselinePosition(TextBaseline.ROMAN);
					
					desiredExtent = glyphAscent  + baselineShift + lineDescent;
					if (horizontalText)
					{
						if (desiredExtent > r.height)
						{
							r.y -= desiredExtent - r.height;
							r.height = desiredExtent;
						}
					}
					else
					{
						if (desiredExtent > r.width)
							r.width = desiredExtent;
					}
				}
				else
				{
					// The rectangle needs to hang from the line's ascent and must extend far enough to accommodate the
					// descender of the glyph (that has moved down because of superscript)
					
					var glyphDescent:Number = -baseStrikethroughOffset; // see assumptions above
					baselineShift = metrics.subscriptOffset * fontSize; 
					var lineAscent:Number = tLine.getBaselinePosition(TextBaseline.ROMAN) - tLine.getBaselinePosition(TextBaseline.ASCENT);
					
					desiredExtent = lineAscent + baselineShift + glyphDescent;
					if (horizontalText)
					{
						if (desiredExtent > r.height)
							r.height = desiredExtent;
					}
					else
					{
						if (desiredExtent > r.width)
						{
							r.x -= desiredExtent - r.width
							r.width = desiredExtent;
						}
					}
				}
			}
			
			tf.backgroundManager.addRect(tLine, this, r, _computedFormat.backgroundColor, _computedFormat.backgroundAlpha);	 
		}

		/** @private */
		tlf_internal function calculateStrikeThrough(textLine:TextLine, blockProgression:String, metrics:FontMetrics):Number
		{
			var underlineAndStrikeThroughShift:int = 0;	
			var effectiveFontSize:Number = this.getEffectiveFontSize()
			if (_computedFormat.baselineShift == BaselineShift.SUPERSCRIPT)
			{
				underlineAndStrikeThroughShift = -(metrics.superscriptOffset * effectiveFontSize);
			} else if (_computedFormat.baselineShift == BaselineShift.SUBSCRIPT)
			{
				underlineAndStrikeThroughShift = -(metrics.subscriptOffset * (effectiveFontSize / metrics.subscriptScale));
			} else {
				underlineAndStrikeThroughShift = TextLayoutFormat.baselineShiftProperty.computeActualPropertyValue(_computedFormat.baselineShift, effectiveFontSize);
			}
			
			//grab the dominantBaseline and alignmentBaseline strings
			var domBaselineString:String = resolveDomBaseline(computedFormat, getParagraph());
			var alignmentBaselineString:String = this.computedFormat.alignmentBaseline;
			
			//this value represents the position of the baseline used to align this text
			var alignDomBaselineAdjustment:Number = textLine.getBaselinePosition(domBaselineString);
			
			//if the alignment baseline differs from the dominant, then we need to apply the delta between the
			//dominant and the alignment to determine the line along which the glyphs are lining up...
			if(alignmentBaselineString != flash.text.engine.TextBaseline.USE_DOMINANT_BASELINE && 
				alignmentBaselineString != domBaselineString)
			{
				alignDomBaselineAdjustment = textLine.getBaselinePosition(alignmentBaselineString);
				//take the alignmentBaseline offset and make it relative to the dominantBaseline
			}
			
			
			//first, establish the offset relative to the glyph based in fontMetrics data
			var stOffset:Number = metrics.strikethroughOffset;
			
			
			//why are we using the stOffset?  Well, the stOffset effectively tells us where the mid-point
			//of the glyph is.  By using this value, we can determine how we need to offset the underline.
			//now adjust the value.  If it is center, then the glyphs are aligned along the ST position already
			if(domBaselineString == flash.text.engine.TextBaseline.IDEOGRAPHIC_CENTER)
			{
				stOffset = 0;
			}
			else if(domBaselineString == flash.text.engine.TextBaseline.IDEOGRAPHIC_TOP || 
				domBaselineString == flash.text.engine.TextBaseline.ASCENT)
			{
				stOffset *= -2;  //if the glyphs are top or ascent, then we need to invert and double the offset
				stOffset -= (2 * metrics.strikethroughThickness);
			}
			else if(domBaselineString == flash.text.engine.TextBaseline.IDEOGRAPHIC_BOTTOM || 
				domBaselineString == flash.text.engine.TextBaseline.DESCENT)
			{
				stOffset *= 2; //if they're bottom, then we need to simply double it
				stOffset += (2 * metrics.strikethroughThickness);
			}
			else //Roman
			{
				stOffset -= metrics.strikethroughThickness;
			}
			
			
			//Now apply the actual dominant baseline position to the offset
			stOffset += (alignDomBaselineAdjustment - underlineAndStrikeThroughShift);
			return stOffset;
		}
		
		/** @private */
		tlf_internal function calculateUnderlineOffset(stOffset:Number, blockProgression:String, metrics:FontMetrics, textLine:TextLine):Number
		{
			var ulOffset:Number = metrics.underlineOffset + metrics.underlineThickness;
			var baseSTAdjustment:Number = metrics.strikethroughOffset;
			
			//based on the stOffset - which really represents the middle of the glyph, set the ulOffset
			//which will always be relative.  Note that simply using the alignDomBaselineAdjustment is not enough
			if(blockProgression != BlockProgression.RL)
				ulOffset += (stOffset - baseSTAdjustment) + metrics.underlineThickness/2;
			else
			{	
				var para:ParagraphElement = this.getParagraph();

				if (para.computedFormat.locale.toLowerCase().indexOf("zh") == 0)
				{
					ulOffset = -ulOffset;
					ulOffset -= (stOffset - baseSTAdjustment + (metrics.underlineThickness*2));
				}
				else
					ulOffset -= (-ulOffset + stOffset + baseSTAdjustment + (metrics.underlineThickness/2));
			}
			
			return ulOffset;
		}
		
		/** @private */
		CONFIG::debug public override function debugCheckFlowElement(depth:int = 0, extraData:String = ""):int
		{
			// debugging function that asserts if the flow element tree is in an invalid state
			
			var rslt:int = super.debugCheckFlowElement(depth," fte:"+getDebugIdentity(_blockElement)+" "+extraData);
			
			// TODO: eventually these tests will be valid for InlineGraphicElement elements as well
			if (!(this is InlineGraphicElement))
			{
				rslt += assert(textLength != 0 || bindableElement || (parent is SubParagraphGroupElementBase && parent.numChildren == 1), "FlowLeafElement with zero textLength must be deleted"); 
				rslt += assert(parent is ParagraphElement || parent is SubParagraphGroupElementBase, "FlowLeafElement must have a ParagraphElement or SubParagraphGroupElementBase parent");
			}
			return rslt;
		}
	}
}
