flex-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cframp...@apache.org
Subject svn commit: r1301625 - in /incubator/flex/whiteboard/Label_Truncation_Fix-SDK-32119: ./ Label.as
Date Fri, 16 Mar 2012 16:16:21 GMT
Author: cframpton
Date: Fri Mar 16 16:16:21 2012
New Revision: 1301625

URL: http://svn.apache.org/viewvc?rev=1301625&view=rev
Log:
Fix for http://bugs.adobe.com/jira/browse/SDK-32119.  Label truncation did not always work correctly if it was taking next from the next TextLine and moving it up to the line that was being truncated.  Note that the same scenerios in RichText are still broken since the truncation algorithm for that is in TLF not Flex.  Label started with the same algorithm but it has been modified as we've found various problems with it.

Added:
    incubator/flex/whiteboard/Label_Truncation_Fix-SDK-32119/
    incubator/flex/whiteboard/Label_Truncation_Fix-SDK-32119/Label.as   (with props)

Added: incubator/flex/whiteboard/Label_Truncation_Fix-SDK-32119/Label.as
URL: http://svn.apache.org/viewvc/incubator/flex/whiteboard/Label_Truncation_Fix-SDK-32119/Label.as?rev=1301625&view=auto
==============================================================================
--- incubator/flex/whiteboard/Label_Truncation_Fix-SDK-32119/Label.as (added)
+++ incubator/flex/whiteboard/Label_Truncation_Fix-SDK-32119/Label.as Fri Mar 16 16:16:21 2012
@@ -0,0 +1,1640 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  Licensed to the Apache Software Foundation (ASF) under one or more
+//  contributor license agreements.  See the NOTICE file distributed with
+//  this work for additional information regarding copyright ownership.
+//  The ASF licenses this file to You under the Apache License, Version 2.0
+//  (the "License"); you may not use this file except in compliance with
+//  the License.  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package spark.components
+{
+
+import flash.display.DisplayObject;
+import flash.display.DisplayObjectContainer;
+import flash.display.Graphics;
+import flash.display.Shape;
+import flash.geom.Rectangle;
+import flash.text.TextFormat;
+import flash.text.engine.EastAsianJustifier;
+import flash.text.engine.ElementFormat;
+import flash.text.engine.FontDescription;
+import flash.text.engine.FontLookup;
+import flash.text.engine.FontMetrics;
+import flash.text.engine.Kerning;
+import flash.text.engine.LineJustification;
+import flash.text.engine.SpaceJustifier;
+import flash.text.engine.TextBaseline;
+import flash.text.engine.TextBlock;
+import flash.text.engine.TextElement;
+import flash.text.engine.TextLine;
+import flash.text.engine.TypographicCase;
+
+import flashx.textLayout.compose.ISWFContext;
+import flashx.textLayout.compose.TextLineRecycler;
+import flashx.textLayout.formats.BaselineShift;
+import flashx.textLayout.formats.TLFTypographicCase;
+
+import mx.core.IEmbeddedFontRegistry;
+import mx.core.IFlexModuleFactory;
+import mx.core.IUIComponent;
+import mx.core.Singleton;
+import mx.core.mx_internal;
+
+import spark.components.supportClasses.TextBase;
+import spark.utils.TextUtil;
+
+use namespace mx_internal;
+
+//--------------------------------------
+//  Styles
+//--------------------------------------
+
+include "../styles/metadata/BasicInheritingTextStyles.as"
+include "../styles/metadata/BasicNonInheritingTextStyles.as"
+
+//--------------------------------------
+//  Other metadata
+//--------------------------------------
+
+[DefaultProperty("text")]
+
+[IconFile("Label.png")]
+
+/**
+ *  Label is a low-level UIComponent that can render
+ *  one or more lines of uniformly-formatted text.
+ *  The text to be displayed is determined by the
+ *  <code>text</code> property inherited from TextBase.
+ *  The formatting of the text is specified by the element's CSS styles,
+ *  such as <code>fontFamily</code> and <code>fontSize</code>.
+ *
+ *  <p>Label uses of the 
+ *  Flash Text Engine (FTE) in Flash Player to provide high-quality
+ *  international typography.
+ *  Because Label is fast and lightweight, it is especially suitable
+ *  for use cases that involve rendering many small pieces of non-interactive
+ *  text, such as item renderers and labels in Button skins.</p>
+ *
+ *  <p>The Spark architecture provides three text "primitives" -- 
+ *  Label, RichText, and RichEditableText --
+ *  as part of its pay-only-for-what-you-need philosophy.
+ *  Label is the fastest and most lightweight,
+ *  but is limited in its capabilities: no complex formatting,
+ *  no scrolling, no selection, no editing, and no hyperlinks.
+ *  RichText and RichEditableText are built on the Text Layout
+ *  Framework (TLF) library, rather than on FTE.
+ *  RichText adds the ability to render rich HTML-like text
+ *  with complex formatting, but is still completely non-interactive.
+ *  RichEditableText is the slowest and heaviest,
+ *  but can do it all: it supports scrolling with virtualized TextLines,
+ *  selection, editing, hyperlinks, and images loaded from URLs.
+ *  You should use the fastest one that meets your needs.</p>
+ *
+ *  <p>The Spark Label control is similar to the MX Label control, mx.controls.Label.
+ *  The most important differences are:
+ *  <ul>
+ *    <li>Spark Label uses FTE, the player's new text engine,
+ *        while MX Label uses the TextField class.</li>
+ *    <li>Spark Label offers better typography, and better support
+ *        for international languages, than MX Label.</li>
+ *    <li>Spark Label can display multiple lines, which MX Label cannot.</li>
+ *    <li>MX Label can display a limited subset of HTML,
+ *        while Spark Label can only display text with uniform formatting.</li>
+ *    <li>MX Label can be made selectable, while Spark Label cannot.</li>
+ *  </ul></p>
+ *
+ *  <p>In Spark Label, three character sequences are recognized
+ *  as explicit line breaks: CR (<code>"\r"</code>), LF (<code>"\n"</code>),
+ *  and CR+LF (<code>"\r\n"</code>).</p>
+ *
+ *  <p>If you don't specify any kind of width for a Label,
+ *  then the longest line, as determined by these explicit line breaks,
+ *  determines the width of the Label.</p>
+ *
+ *  <p>If you do specify some kind of width, then the specified text is
+ *  word-wrapped at the right edge of the component's bounds, because the
+ *  default value of the <code>lineBreak</code> style is <code>"toFit"</code>.
+ *  If the text extends below the bottom of the component,
+ *  it is clipped.</p>
+ *
+ *  <p>To disable this automatic wrapping, set the <code>lineBreak</code>
+ *  style to <code>"explicit"</code>. Then lines are broken only where
+ *  the <code>text</code> contains an explicit line break,
+ *  and the ends of lines extending past the right edge is clipped.</p>
+ *
+ *  <p>If you have more text than you have room to display it,
+ *  Label can truncate the text for you.
+ *  Truncating text means replacing excess text
+ *  with a truncation indicator such as "...".
+ *  See the inherited properties <code>maxDisplayedLines</code>
+ *  and <code>isTruncated</code>.</p>
+ *
+ *  <p>You can control the line spacing with the <code>lineHeight</code> style.
+ *  You can horizontally and vertically align the text within the element's
+ *  bounds using the <code>textAlign</code>, <code>textAlignLast</code>,
+ *  and <code>verticalAlign</code> styles.
+ *  You can inset the text from the element's edges using the
+ *  <code>paddingLeft</code>, <code>paddingTop</code>, 
+ *  <code>paddingRight</code>, and <code>paddingBottom</code> styles.</p>
+ *
+ *  <p>By default a Label has no background,
+ *  but you can draw one using the <code>backgroundColor</code>
+ *  and <code>backgroundAlpha</code> styles.
+ *  Borders are not supported.
+ *  If you need a border, or a more complicated background, use a separate
+ *  graphic element, such as a Rect, behind the Label.</p>
+ *
+ *  <p>Label supports displaying left-to-right (LTR) text such as French,
+ *  right-to-left (RTL) text such as Arabic, and bidirectional text
+ *  such as a French phrase inside of an Arabic paragraph.
+ *  If the predominant text direction is right-to-left,
+ *  set the <code>direction</code> style to <code>"rtl"</code>.
+ *  The <code>textAlign</code> style defaults to <code>"start"</code>,
+ *  which makes the text left-aligned when <code>direction</code>
+ *  is <code>"ltr"</code> and right-aligned when <code>direction</code>
+ *  is <code>"rtl"</code>.
+ *  To get the opposite alignment,
+ *  set <code>textAlign</code> to <code>"end"</code>.</p>
+ *
+ *  <p>Label uses the TextBlock class in the Flash Text Engine
+ *  to create one or more TextLine objects to statically display
+ *  its text String in the format determined by its CSS styles.
+ *  For performance, its TextLines do not contain information
+ *  about individual glyphs; for more info, see
+ *  flash.text.engine.TextLineValidity.STATIC.</p>
+ *
+ *  <p>The Label control has the following default characteristics:</p>
+ *  <table class="innertable">
+ *     <tr><th>Characteristic</th><th>Description</th></tr>
+ *     <tr><td>Default size</td><td>0 pixels wide by 12 pixels high if it contains no text, 
+ *        and large enough ti display the text if it does</td></tr>
+ *     <tr><td>Minimum size</td><td>0 pixels</td></tr>
+ *     <tr><td>Maximum size</td><td>10000 pixels wide and 10000 pixels high</td></tr>
+ *  </table>
+ *
+ *  @mxml <p>The <code>&lt;s:Label&gt;</code> tag inherits all of the tag 
+ *  attributes of its superclass and adds the following tag attributes:</p>
+ *
+ *  <pre>
+ *  &lt;s:Label 
+ *    <strong>Properties</strong>
+ *    fontContext=""
+ * 
+ *    <strong>Styles</strong>
+ *    alignmentBaseline="baseline"
+ *    baselineShift="0"
+ *    cffHinting="0.0"
+ *    color="0x000000"
+ *    digitCase="default"
+ *    digitWidth="default"
+ *    direction="ltr"
+ *    dominantBaseline="auto"
+ *    fontFamily="Arial"
+ *    fontLookup="embeddedCFF"
+ *    fontSize="12"
+ *    fontStyle="normal"
+ *    fontWeight="normal"
+ *    justificationRule="auto"
+ *    justificationStyle="auto"
+ *    kerning="false"
+ *    ligatureLevel="common"
+ *    lineBreak="toFit"
+ *    lineHeight="120%"
+ *    lineThrough="false"
+ *    locale="en"
+ *    paddingBottom="0"
+ *    paddingLeft="0"
+ *    paddingRight="0"
+ *    paddingTop="0"
+ *    renderingMode="cff"
+ *    textAlign="start"
+ *    textAlignLast="start"
+ *    textAlpha="1"
+ *    textDecoration="start"
+ *    textJustify="interWord"
+ *    trackingLeft="0"
+ *    trackingRight="00"
+ *    typographicCase="default"
+ *    verticalAlign="top"
+ *  /&gt;
+ *  </pre>
+ *
+ *  @see spark.components.RichEditableText
+ *  @see spark.components.RichText
+ *  @see flash.text.engine.TextLineValidity#STATIC
+ *
+ *  @includeExample examples/LabelExample.mxml
+ *  
+ *  @langversion 3.0
+ *  @playerversion Flash 10
+ *  @playerversion AIR 1.5
+ *  @productversion Flex 4
+ */
+public class Label extends TextBase
+{
+    include "../core/Version.as";
+
+    //--------------------------------------------------------------------------
+    //
+    //  Class initialization
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  @private
+     */
+    private static function initClass():void
+    {
+        staticTextBlock = new TextBlock();
+        
+        staticTextElement = new TextElement();
+        
+        staticSpaceJustifier = new SpaceJustifier();
+        
+        staticEastAsianJustifier = new EastAsianJustifier();
+        
+        if ("recreateTextLine" in staticTextBlock)
+            recreateTextLine = staticTextBlock["recreateTextLine"];
+    }
+    
+    initClass();
+
+    //--------------------------------------------------------------------------
+    //
+    //  Class variables
+    //
+    //--------------------------------------------------------------------------
+        
+    // We can re-use single instances of a few FTE classes over and over,
+    // since they just serve as a factory for the TextLines that we care about.
+    
+    /**
+     *  @private
+     */
+    private static var staticTextBlock:TextBlock;
+
+    /**
+     *  @private
+     */
+    private static var staticTextElement:TextElement;
+
+    /**
+     *  @private
+     */
+    private static var staticSpaceJustifier:SpaceJustifier;
+
+    /**
+     *  @private
+     */
+    private static var staticEastAsianJustifier:EastAsianJustifier;
+    
+    /**
+     *  @private
+     *  A reference to the recreateTextLine() method in staticTextBlock,
+     *  if it exists. This method was added in player 10.1.
+     *  It allows better performance by making it possible to reuse
+     *  existing TextLines instead of having to create new ones.
+     */
+    private static var recreateTextLine:Function;
+
+    //--------------------------------------------------------------------------
+    //
+    //  Class properties
+    //
+    //--------------------------------------------------------------------------
+
+    //----------------------------------
+    //  embeddedFontRegistry
+    //----------------------------------
+
+    /**
+     *  @private
+     *  Storage for the _embeddedFontRegistry property.
+     *  Note: This gets initialized on first access,
+     *  not when this class is initialized, in order to ensure
+     *  that the Singleton registry has already been initialized.
+     */
+    private static var _embeddedFontRegistry:IEmbeddedFontRegistry;
+
+    /**
+     *  @private
+     *  A reference to the embedded font registry.
+     *  Single registry in the system.
+     *  Used to look up the moduleFactory of a font.
+     */
+    private static function get embeddedFontRegistry():IEmbeddedFontRegistry
+    {
+        if (!_embeddedFontRegistry)
+        {
+            _embeddedFontRegistry = IEmbeddedFontRegistry(
+                Singleton.getInstance("mx.core::IEmbeddedFontRegistry"));
+        }
+
+        return _embeddedFontRegistry;
+    }
+
+    //--------------------------------------------------------------------------
+    //
+    //  Class methods
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  @private
+     */
+    private static function getNumberOrPercentOf(value:Object,
+                                                 n:Number):Number
+    {
+        // If 'value' is a Number like 10.5, return it.
+        if (value is Number)
+            return Number(value);
+
+        // If 'value' is a percentage String like "10.5%",
+        // return that percentage of 'n'.
+        if (value is String)
+        {
+            var len:int = String(value).length;
+            if (len >= 1 && value.charAt(len - 1) == "%")
+            {
+                var percent:Number = Number(value.substring(0, len - 1));
+                return percent / 100 * n;
+            }
+        }
+
+        // Otherwise, return NaN.
+        return NaN;
+    }
+
+    //--------------------------------------------------------------------------
+    //
+    //  Constructor
+    //
+    //--------------------------------------------------------------------------
+
+    /**
+     *  Constructor. 
+     *  
+     *  @langversion 3.0
+     *  @playerversion Flash 10
+     *  @playerversion AIR 1.5
+     *  @productversion Flex 4
+     */
+    public function Label()
+    {
+        super();
+    }
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Variables
+    //
+    //--------------------------------------------------------------------------
+
+    /**
+     *  @private
+     *  Holds the last recorded value of the module factory
+     *  used to create the font.
+     */
+    private var embeddedFontContext:IFlexModuleFactory;
+    
+    /**
+     *  @private
+     *  When we render the text using FTE, this object represents the formatting 
+     *  for our text element(s). Every time format related styles change, this 
+     *  object is released because it is invalid. It is regenerated just in time 
+     *  to render the text.
+     */
+    private var elementFormat:ElementFormat;
+
+    //--------------------------------------------------------------------------
+    //
+    //  Overidden Methods: ISimpleStyleClient
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  @private
+     */
+    override public function stylesInitialized():void
+    {
+        super.stylesInitialized();
+        elementFormat = null;
+    }
+    
+    /**
+     *  @private
+     */
+    override public function styleChanged(styleProp:String):void
+    {
+        super.styleChanged(styleProp);
+        elementFormat = null;
+    }
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Overridden methods: TextBase
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  @private
+     *  This helper method is used by measure() and updateDisplayList().
+     *  It composes TextLines to render the 'text' String,
+     *  using the staticTextBlock as a factory,
+     *  and using the 'width' and 'height' parameters to define the size
+     *  of the composition rectangle, with NaN meaning "no limit".
+     *  It stops composing when the composition rectangle has been filled.
+     *  Returns true if all lines were composed, otherwise false.
+     */
+    override mx_internal function composeTextLines(width:Number = NaN,
+                                                   height:Number = NaN):Boolean
+    {
+        super.composeTextLines(width, height);
+        
+        if (!elementFormat)
+            elementFormat = createElementFormat(); 
+            
+        // Set the composition bounds to be used by createTextLines().
+        // If the width or height is NaN, it will be computed by this method
+        // by the time it returns.
+        // The bounds are then used by the addTextLines() method
+        // to determine the isOverset flag.
+        // The composition bounds are also reported by the measure() method.
+        bounds.x = 0;
+        bounds.y = 0;
+        bounds.width = width;
+        bounds.height = height;
+
+        // Remove the TextLines from the container and then release them for
+        // reuse, if supported by the player.
+        removeTextLines();
+        releaseTextLines();
+        
+        // Create the TextLines.
+        var allLinesComposed:Boolean = createTextLines(elementFormat);
+        
+        // Need truncation if all the following are true
+        // - there is text (even if there is no text there is may be padding
+        //       which may not fit and the text would be reported as truncated)
+        // - truncation options exist (0=no trunc, -1=fill up bounds then trunc,
+        //      n=n lines then trunc)
+        // - compose width is specified
+        // - content doesn't fit
+        var lb:String = getStyle("lineBreak");
+        if (text != null && text.length > 0 &&
+            maxDisplayedLines &&
+            !doesComposedTextFit(height, width, allLinesComposed, maxDisplayedLines, lb))
+        {
+            truncateText(width, height, lb);
+        }
+        
+        // Detach the TextLines from the TextBlock that created them.
+        releaseLinesFromTextBlock();
+                                                       
+        // Add the new text lines to the container.
+        addTextLines();
+
+        // Figure out if a scroll rect is needed.
+        isOverset = isTextOverset(width, height);
+        
+        // Just recomposed so reset.
+        invalidateCompose = false;     
+        
+        return allLinesComposed;           
+    }
+
+    //--------------------------------------------------------------------------
+    //
+    //  Methods
+    //
+    //--------------------------------------------------------------------------
+
+    /**
+     *  @private
+     *  Creates an ElementFormat (and its FontDescription)
+     *  based on the Label's CSS styles.
+     *  These must be recreated each time because FTE
+     *  does not allow them to be reused.
+     *  As a side effect, this method also sets embeddedFontContext
+     *  so that we know which SWF should be used to create TextLines.
+     *  (TextLines using an embedded font must be created in the SWF
+     *  where the font is.)
+     */
+    private function createElementFormat():ElementFormat
+    {
+        // When you databind to a text formatting style on a Label,
+        // as in <Label fontFamily="{fontCombo.selectedItem}"/>
+        // the databinding can cause the style to be set to null.
+        // Setting null values for properties in an FTE FontDescription
+        // or ElementFormat throw an error, so the following code does
+        // null-checking on the problematic properties.
+
+        var s:String;
+        
+        // If the CSS styles for this component specify an embedded font,
+        // embeddedFontContext will be set to the module factory that
+        // should create TextLines (since they must be created in the
+        // SWF where the embedded font is.)
+        // Otherwise, this will be null.
+        embeddedFontContext = getEmbeddedFontContext();
+
+        // Fill out a FontDescription based on the CSS styles.
+        
+        var fontDescription:FontDescription = new FontDescription();
+        
+        s = getStyle("cffHinting");
+        if (s != null)
+            fontDescription.cffHinting = s;
+        
+        s = getStyle("fontLookup");
+        if (s != null)
+        {
+            // FTE understands only "device" and "embeddedCFF"
+            // for fontLookup. But Flex allows this style to be
+            // set to "auto", in which case we automatically
+            // determine it based on whether the CSS styles
+            // specify an embedded font.
+            if (s == "auto")
+            {
+                s = embeddedFontContext ?
+                    FontLookup.EMBEDDED_CFF :
+                    FontLookup.DEVICE;
+            }
+            else if (s == FontLookup.EMBEDDED_CFF && !embeddedFontContext)
+            {
+                // If the embedded font isn't found, fall back to device font,
+                // before falling back to the player's default font.
+                s = FontLookup.DEVICE;
+            }
+            fontDescription.fontLookup = s;
+        }
+        
+        s = getStyle("fontFamily");
+        if (s != null)
+            fontDescription.fontName = s;
+        
+        s = getStyle("fontStyle");
+        if (s != null)
+            fontDescription.fontPosture = s;
+        
+        s = getStyle("fontWeight");
+        if (s != null)
+            fontDescription.fontWeight = s;
+            
+        s = getStyle("renderingMode");
+        if (s != null)
+            fontDescription.renderingMode = s;
+        
+        // Fill our an ElementFormat based on the CSS styles.
+        
+        var elementFormat:ElementFormat = new ElementFormat();
+        
+        // Out of order so it can be used by baselineShift.
+        elementFormat.fontSize = getStyle("fontSize");
+        
+        s = getStyle("alignmentBaseline");
+        if (s != null)
+            elementFormat.alignmentBaseline = s;
+            
+        elementFormat.alpha = getStyle("textAlpha");
+            
+        setBaselineShift(elementFormat);
+        
+        // Note: Label doesn't support a breakOpportunity style,
+        // so we leave elementFormat.breakOpportunity with its
+        // default value of "auto".
+            
+        elementFormat.color = getStyle("color");
+        
+        s = getStyle("digitCase");
+        if (s != null)
+            elementFormat.digitCase = s;
+            
+        s = getStyle("digitWidth");
+        if (s != null)
+            elementFormat.digitWidth = s;
+            
+        s = getStyle("dominantBaseline");
+        if (s != null)
+        {
+            // TLF adds the concept of a locale-based "auto" setting for
+            // dominantBaseline, so we support that in Label as well
+            // so that "auto" can be used in the global selector.
+            // TLF's rule is that "auto" means "ideographicCenter"
+            // for Japanese and Chinese locales and "roman" for other locales.
+            // (See TLF's LocaleUtil, which we avoid linking in here.)
+            if (s == "auto")
+            {
+                s = TextBaseline.ROMAN;
+                var locale:String = getStyle("locale");
+                if (locale != null)
+                {
+                    var lowercaseLocale:String = locale.toLowerCase();
+                    if (lowercaseLocale.indexOf("ja") == 0 ||
+                        lowercaseLocale.indexOf("zh") == 0)
+                    {
+                        s = TextBaseline.IDEOGRAPHIC_CENTER;
+                    }
+                }
+            }
+            elementFormat.dominantBaseline = s;
+        }
+            
+        elementFormat.fontDescription = fontDescription;
+        
+        setKerning(elementFormat);
+
+        s = getStyle("ligatureLevel");
+        if (s != null)
+            elementFormat.ligatureLevel = s;
+        
+        s = getStyle("locale");
+        if (s != null)
+            elementFormat.locale = s;
+        
+        setTracking(elementFormat);
+
+        setTypographicCase(elementFormat);
+        
+        return elementFormat;
+    }
+
+    /**
+     *  @private
+     */
+    private function setBaselineShift(elementFormat:ElementFormat):void
+    {
+        var baselineShift:* = getStyle("baselineShift");
+        var fontSize:Number = elementFormat.fontSize;
+        
+        if (baselineShift == BaselineShift.SUPERSCRIPT || 
+            baselineShift == BaselineShift.SUBSCRIPT)
+        {
+            var fontMetrics:FontMetrics;
+            if (embeddedFontContext)
+                fontMetrics = embeddedFontContext.callInContext(elementFormat.getFontMetrics, elementFormat, null);
+            else
+                fontMetrics = elementFormat.getFontMetrics();
+            if (baselineShift == BaselineShift.SUPERSCRIPT)
+            {
+                elementFormat.baselineShift = 
+                    fontMetrics.superscriptOffset * fontSize;
+                elementFormat.fontSize = fontMetrics.superscriptScale * fontSize;
+            }
+            else // it's subscript
+            {
+                elementFormat.baselineShift = 
+                    fontMetrics.subscriptOffset * fontSize;
+                elementFormat.fontSize = fontMetrics.subscriptScale * fontSize;
+            }
+        }			
+        else
+        {
+            // TLF will throw a range error if percentage not between
+            // -1000% and 1000%.  Label does not.
+            baselineShift = 
+                getNumberOrPercentOf(baselineShift, fontSize);
+            if (!isNaN(baselineShift))
+                elementFormat.baselineShift = -baselineShift;
+                    // Note: The negative sign is because, as in TLF,
+                    // we want a positive number to shift the baseline up,
+                    // whereas FTE does it the opposite way.
+                    // In FTE, a positive baselineShift increases
+                    // the y coordinate of the baseline, which is
+                    // mathematically appropriate, but unintuitive.    
+        }
+    }
+    
+    /**
+     *  @private
+     */
+    private function setKerning(elementFormat:ElementFormat):void
+    {
+        var kerning:Object = getStyle("kerning");
+        
+        // In Halo components based on TextField,
+        // kerning is supposed to be true or false.
+        // The default in TextField and Flex 3 is false
+        // because kerning doesn't work for device fonts
+        // and is slow for embedded fonts.
+        // In Spark components based on TLF and FTE,
+        // kerning is "auto", "on", or, "off".
+        // The default in TLF and FTE is "auto"
+        // (which means kern non-Asian characters)
+        // because kerning works even on device fonts
+        // and has miminal performance impact.
+        // Since a CSS selector or parent container
+        // can affect both Halo and Spark components,
+        // we need to map true to "on" and false to "off"
+        // here and in Label.
+        // For Halo components, UITextField and UIFTETextField
+        // do the opposite mapping
+        // of "auto" and "on" to true and "off" to false.
+        // We also support a value of "default"
+        // (which we set in the global selector)
+        // to mean "auto" for Spark and false for Halo
+        // to get the recommended behavior in both sets of components.
+        if (kerning === "default")
+            kerning = Kerning.AUTO;
+        else if (kerning === true)
+            kerning = Kerning.ON;
+        else if (kerning === false)
+            kerning = Kerning.OFF;
+        
+        if (kerning != null)
+           elementFormat.kerning = String(kerning);
+    }
+
+    /**
+     *  @private
+     */
+    private function setTracking(elementFormat:ElementFormat):void
+    {
+        var trackingLeft:Object = getStyle("trackingLeft");
+        var trackingRight:Object = getStyle("trackingRight");
+        
+        var value:Number;
+        var fontSize:Number = elementFormat.fontSize;
+       
+        value = getNumberOrPercentOf(trackingLeft, fontSize);
+        if (!isNaN(value))
+            elementFormat.trackingLeft = value;
+
+        value = getNumberOrPercentOf(trackingRight, fontSize);
+        if (!isNaN(value))
+            elementFormat.trackingRight = value;
+    }
+
+    /**
+     *  @private
+     */
+    private function setTypographicCase(elementFormat:ElementFormat):void
+    {
+        var s:String = getStyle("typographicCase");
+        if (s != null)
+        {
+            switch (s)
+            {
+                case TLFTypographicCase.LOWERCASE_TO_SMALL_CAPS:
+                {
+                    elementFormat.typographicCase = 
+                        TypographicCase.CAPS_AND_SMALL_CAPS;
+                    break;
+                }
+                case TLFTypographicCase.CAPS_TO_SMALL_CAPS:
+                {
+                    elementFormat.typographicCase = TypographicCase.SMALL_CAPS;
+                    break;
+                }
+                default:
+                {
+                    // Others map directly so handle it in the default case.
+                    elementFormat.typographicCase = s;
+                    break;
+                }
+            }
+        }        
+    }
+
+    
+    /**
+     *  @private
+     *  Stuffs the specified text and formatting info into a TextBlock
+     *  and uses it to create as many TextLines as fit into the bounds.
+     *  Returns true if all the text was composed into textLines.
+     */
+    private function createTextLines(elementFormat:ElementFormat):Boolean
+    {
+        // Get CSS styles that affect a TextBlock and its justifier.
+        var direction:String = getStyle("direction");
+        var justificationRule:String = getStyle("justificationRule");
+        var justificationStyle:String = getStyle("justificationStyle");
+        var textAlign:String = getStyle("textAlign");
+        var textAlignLast:String = getStyle("textAlignLast");
+        var textJustify:String = getStyle("textJustify");
+
+        // TLF adds the concept of a locale-based "auto" setting for
+        // justificationRule and justificationStyle, so we support
+        // that in Label as well so that "auto" can be used
+        // in the global selector.
+        // TLF's rule is that "auto" for justificationRule means "eastAsian"
+        // for Japanese and Chinese locales and "space" for other locales,
+        // and that "auto" for justificationStyle (which only affects
+        // the EastAsianJustifier) always means "pushInKinsoku".
+        // (See TLF's LocaleUtil, which we avoid linking in here.)
+        if (justificationRule == "auto")
+        {
+            justificationRule = "space";
+            var locale:String = getStyle("locale");
+            if (locale != null)
+            {
+                var lowercaseLocale:String = locale.toLowerCase();
+                if (lowercaseLocale.indexOf("ja") == 0 ||
+                    lowercaseLocale.indexOf("zh") == 0)
+                {
+                    justificationRule = "eastAsian";
+                }
+            }
+        }
+        if (justificationStyle == "auto")
+            justificationStyle = "pushInKinsoku";
+
+        // Set the TextBlock's content.
+        // Note: If there is no text, we do what TLF does and compose
+        // a paragraph terminator character, so that a TextLine
+        // gets created and we can measure it.
+        // It will have a width of 0 but a height equal
+        // to the font's ascent plus descent.
+        staticTextElement.text = text != null && text.length > 0 ? text : "\u2029";
+        staticTextElement.elementFormat = elementFormat;
+        staticTextBlock.content = staticTextElement;
+
+        // And its bidiLevel.
+        staticTextBlock.bidiLevel = direction == "ltr" ? 0 : 1;
+
+        // And its justifier.
+        var lineJustification:String;
+        if (textAlign == "justify")
+        {
+            lineJustification = textAlignLast == "justify" ?
+                                LineJustification.ALL_INCLUDING_LAST :
+                                LineJustification.ALL_BUT_LAST;
+        }
+        else
+        {
+            lineJustification = LineJustification.UNJUSTIFIED;
+        }
+        if (justificationRule == "space")
+        {
+            staticSpaceJustifier.lineJustification = lineJustification;
+            staticSpaceJustifier.letterSpacing = textJustify == "distribute";
+            staticTextBlock.textJustifier = staticSpaceJustifier;
+        }
+        else
+        {
+            staticEastAsianJustifier.lineJustification = lineJustification;
+            staticEastAsianJustifier.justificationStyle = justificationStyle;
+            
+            staticTextBlock.textJustifier = staticEastAsianJustifier;
+        }
+                
+        // Then create TextLines using this TextBlock.
+        return createTextLinesFromTextBlock(staticTextBlock, textLines, bounds);
+    }
+
+    /**
+     *  @private
+     *  Compose into textLines.  bounds on input is size of composition
+     *  area and on output is the size of the composed content.
+     *  The caller must call releaseLinesFromTextBlock() to release the
+     *  textLines from the TextBlock.  This must be done after truncation
+     *  so that the composed lines can be broken into atoms to figure out
+     *  where the truncation indicator should be placed.
+     * 
+     *  Returns true if all the text was composed into textLines.
+     */
+    private function createTextLinesFromTextBlock(textBlock:TextBlock,
+                                                  textLines:Vector.<DisplayObject>,
+                                                  bounds:Rectangle):Boolean
+    {
+        // Start with 0 text lines.
+        releaseTextLines(textLines);
+           
+        // Get CSS styles for formats that we have to apply ourselves.
+        var direction:String = getStyle("direction");
+        var lineBreak:String = getStyle("lineBreak");
+        var lineHeight:Object = getStyle("lineHeight");
+        var lineThrough:Boolean = getStyle("lineThrough");
+        var paddingBottom:Number = getStyle("paddingBottom");
+        var paddingLeft:Number = getStyle("paddingLeft");
+        var paddingRight:Number = getStyle("paddingRight");
+        var paddingTop:Number = getStyle("paddingTop");
+        var textAlign:String = getStyle("textAlign");
+        var textAlignLast:String = getStyle("textAlignLast");
+        var textDecoration:String = getStyle("textDecoration");
+        var verticalAlign:String = getStyle("verticalAlign");
+
+        var innerWidth:Number = bounds.width - paddingLeft - paddingRight;
+        var innerHeight:Number = bounds.height - paddingTop - paddingBottom;
+                
+        var measureWidth:Boolean = isNaN(innerWidth);
+        if (measureWidth)
+            innerWidth = maxWidth;
+        
+        var maxLineWidth:Number = lineBreak == "explicit" ?
+                                  TextLine.MAX_LINE_WIDTH :
+                                  innerWidth;
+        
+        if (innerWidth < 0 || innerHeight < 0 || !textBlock)
+        {
+            bounds.width = 0;
+            bounds.height = 0;
+            return false;
+        }
+
+        var fontSize:Number = staticTextElement.elementFormat.fontSize;
+        var actualLineHeight:Number;
+        if (lineHeight is Number)
+        {
+            actualLineHeight = Number(lineHeight);
+        }
+        else if (lineHeight is String)
+        {
+            var len:int = lineHeight.length;
+            var percent:Number =
+                Number(String(lineHeight).substring(0, len - 1));
+            actualLineHeight = percent / 100 * fontSize;
+        }
+        if (isNaN(actualLineHeight))
+            actualLineHeight = 1.2 * fontSize;
+        
+        var maxTextWidth:Number = 0;
+        var totalTextHeight:Number = 0;
+        var n:int = 0;
+        var nextTextLine:TextLine;
+        var nextY:Number = 0;
+        var textLine:TextLine;
+        
+        var swfContext:ISWFContext = ISWFContext(embeddedFontContext);
+                    
+        // For truncation, need to know if all lines have been composed.
+        var createdAllLines:Boolean = false;
+        // sometimes we need to create an extra line in order to compute
+        // truncation
+        var extraLine:Boolean;
+        
+        // Generate TextLines, stopping when we run out of text
+        // or reach the bottom of the requested bounds.
+        // In this loop the lines are positioned within the rectangle
+        // (0, 0, innerWidth, innerHeight), with top-left alignment.
+        while (true)
+        {
+            var recycleLine:TextLine = TextLineRecycler.getLineForReuse();
+            if (recycleLine)
+            {
+                if (swfContext)
+                {
+                    nextTextLine = swfContext.callInContext(
+                        textBlock["recreateTextLine"], textBlock,
+                        [ recycleLine, textLine, maxLineWidth ]);       
+                }        
+                else
+                {
+                    nextTextLine = recreateTextLine(
+                        recycleLine, textLine, maxLineWidth);
+                }  
+            }
+            else
+            {
+                if (swfContext)
+                {
+                    nextTextLine = swfContext.callInContext(
+                        textBlock.createTextLine, textBlock,
+                        [ textLine, maxLineWidth ]);
+                }
+                else
+                {
+                    nextTextLine = textBlock.createTextLine(
+                        textLine, maxLineWidth);
+                }
+            }
+            
+            if (!nextTextLine)
+            {
+                createdAllLines = !extraLine;
+                break;
+            }
+            
+            // Determine the natural baseline position for this line.
+            // Note: The y coordinate of a TextLine is the location
+            // of its baseline, not of its top.
+            nextY += (n == 0 ? nextTextLine.ascent : actualLineHeight);
+            
+            // If verticalAlign is top and the next line is completely outside 
+            // the rectangle, we're done.  If verticalAlign is middle or bottom
+            // then we need to compose all the lines so the alignment is done
+            // correctly.
+            if (verticalAlign == "top" && 
+                nextY - nextTextLine.ascent > innerHeight)
+            {
+                // make an extra line so we can compute truncation
+                if (!extraLine) 
+                    extraLine = true;
+                else
+                    break;
+            }
+
+            // We'll keep this line. Put it into the textLines array.
+            textLine = nextTextLine;
+            textLines[n++] = textLine;
+
+            // Assign its location based on left/top alignment.
+            // Its x position is 0 by default.
+            textLine.y = nextY;
+            
+            // Keep track of the maximum textWidth 
+            // and the accumulated textHeight of the TextLines.
+            maxTextWidth = Math.max(maxTextWidth, textLine.textWidth);
+            totalTextHeight += textLine.textHeight;
+
+            if (lineThrough || textDecoration == "underline")
+            {
+                // FTE doesn't render strikethroughs or underlines,
+                // but it can tell us where to draw them.
+                // You can't draw in a TextLine but it can have children,
+                // so we create a child Shape to draw them in.
+                
+                var elementFormat:ElementFormat =
+                    TextElement(textBlock.content).elementFormat;
+                var fontMetrics:FontMetrics;
+                if (embeddedFontContext)
+                    fontMetrics = embeddedFontContext.callInContext(elementFormat.getFontMetrics, elementFormat, null);
+                else
+                    fontMetrics = elementFormat.getFontMetrics();
+                
+                var shape:Shape = new Shape();
+                var g:Graphics = shape.graphics;
+                if (lineThrough)
+                {
+                    g.lineStyle(fontMetrics.strikethroughThickness, 
+                                elementFormat.color, elementFormat.alpha);
+                    g.moveTo(0, fontMetrics.strikethroughOffset);
+                    g.lineTo(textLine.textWidth, fontMetrics.strikethroughOffset);
+                }
+                if (textDecoration == "underline")
+                {
+                    g.lineStyle(fontMetrics.underlineThickness, 
+                                elementFormat.color, elementFormat.alpha);
+                    g.moveTo(0, fontMetrics.underlineOffset);
+                    g.lineTo(textLine.textWidth, fontMetrics.underlineOffset);
+                }
+                
+                textLine.addChild(shape);
+            }
+        }
+
+        // At this point, n is the number of lines that fit
+        // and textLine is the last line that fit.
+
+        if (n == 0)
+        {
+            bounds.width = paddingLeft + paddingRight;
+            bounds.height = paddingTop + paddingBottom;
+            return false;
+        }
+        
+        // If not measuring the width, innerWidth remains the same since 
+        // alignment is done over the innerWidth not over the width of the
+        // text that was just composed.
+        if (measureWidth)
+            innerWidth = maxTextWidth;
+
+        if (isNaN(bounds.height))
+            innerHeight = textLine.y + textLine.descent;
+        
+        // Ensure we snap for consistent results.
+        innerWidth = Math.ceil(innerWidth);
+        innerHeight = Math.ceil(innerHeight);
+        
+        var leftAligned:Boolean = 
+            textAlign == "start" && direction == "ltr" ||
+            textAlign == "end" && direction == "rtl" ||
+            textAlign == "left" ||
+            textAlign == "justify";
+        var centerAligned:Boolean = textAlign == "center";
+        var rightAligned:Boolean =
+            textAlign == "start" && direction == "rtl" ||
+            textAlign == "end" && direction == "ltr" ||
+            textAlign == "right"; 
+
+        // Calculate loop constants for horizontal alignment.
+        var leftOffset:Number = bounds.left + paddingLeft;
+        var centerOffset:Number = leftOffset + innerWidth / 2;
+        var rightOffset:Number =  leftOffset + innerWidth;
+        
+        // Calculate loop constants for vertical alignment.
+        var topOffset:Number = bounds.top + paddingTop;
+        var bottomOffset:Number = innerHeight - (textLine.y + textLine.descent);
+        var middleOffset:Number = bottomOffset / 2;
+        bottomOffset += topOffset;
+        middleOffset += topOffset;
+        var leading:Number = (innerHeight - totalTextHeight) / (n - 1);
+        
+        var previousTextLine:TextLine;
+        var y:Number = 0;
+
+        var lastLineIsSpecial:Boolean =
+            textAlign == "justify" && createdAllLines;
+
+        var minX:Number = innerWidth;
+        var minY:Number = innerHeight;
+        var maxX:Number = 0;
+        
+        var clipping:Boolean = (n) ? (textLines[n - 1].y + TextLine(textLines[n - 1]).descent > innerHeight) : false;
+
+        // Reposition each line if necessary.
+        // based on the horizontal and vertical alignment.
+        for (var i:int = 0; i < n; i++)
+        {
+            textLine = TextLine(textLines[i]);
+
+            // If textAlign is "justify" and there is more than one line,
+            // the last one (if we created it) gets horizontal aligned
+            // according to textAlignLast.
+            if (lastLineIsSpecial && i == n - 1)
+            {
+                leftAligned = 
+                    textAlignLast == "start" && direction == "ltr" ||
+                    textAlignLast == "end" && direction == "rtl" ||
+                    textAlignLast == "left" ||
+                    textAlignLast == "justify";
+                centerAligned = textAlignLast == "center";
+                rightAligned =
+                    textAlignLast == "start" && direction == "rtl" ||
+                    textAlignLast == "end" && direction == "ltr" ||
+                    textAlignLast == "right";
+            } 
+
+            if (leftAligned)
+                textLine.x = leftOffset;
+            else if (centerAligned)
+                textLine.x = centerOffset - textLine.textWidth / 2;
+            else if (rightAligned)
+                textLine.x = rightOffset - textLine.textWidth;            
+            
+            if (verticalAlign == "top" || !createdAllLines || clipping)
+            {
+                textLine.y += topOffset;
+            }
+            else if (verticalAlign == "middle")
+            {
+                textLine.y += middleOffset;
+            }
+            else if (verticalAlign == "bottom")
+            {
+                textLine.y += bottomOffset;
+            }
+            else if (verticalAlign == "justify")
+            {
+                // Determine the natural baseline position for this line.
+                // Note: The y coordinate of a TextLine is the location
+                // of its baseline, not of its top.
+                y += i == 0 ?
+                     topOffset + textLine.ascent :
+                     previousTextLine.descent + leading + textLine.ascent;
+            
+                textLine.y = y;
+                previousTextLine = textLine;
+            }
+
+            // Upper left corner of bounding box may not be 0,0 after
+            // styles are applied or rounding error from minY calculation.
+            // y is one decimal place and ascent isn't rounded so minY can be 
+            // slightly less than zero. 
+            minX = Math.min(minX, textLine.x);             
+            minY = Math.min(minY, textLine.y - textLine.ascent);
+            maxX = Math.max(maxX, textLine.x + textLine.textWidth); 
+        }
+
+        bounds.x = minX - paddingLeft;
+        bounds.y = minY - paddingTop;
+        bounds.right = maxX + paddingRight;
+        bounds.bottom = textLine.y + textLine.descent + paddingBottom;
+        
+        return createdAllLines;
+    }
+ 
+    /**
+     *  @private
+     *  Create textLine using paragraph terminator "\u2029" so it creates one
+     *  text line that we can use to get the baseline.  The height is 
+     *  important if the text is vertically aligned.
+     */ 
+    override mx_internal function createEmptyTextLine(height:Number=NaN):void
+    {
+        staticTextElement.text = "\u2029";
+        
+        bounds.width = NaN;
+        bounds.height = height;
+        
+        createTextLinesFromTextBlock(staticTextBlock, textLines, bounds);
+        
+        releaseLinesFromTextBlock();
+    }
+    
+    /**
+     *  @private
+     *  Determines if the composed text fits in the given height and 
+     *  line count limit. 
+     */ 
+    private function doesComposedTextFit(height:Number, width:Number,
+                                         createdAllLines:Boolean,
+                                         lineCountLimit:int, lineBreak:String):Boolean
+    {
+        // Not all text composed because it didn't fit within bounds.
+        if (!createdAllLines)
+            return false;
+                    
+        // More text lines than allowed lines.                    
+        if (lineCountLimit != -1 && textLines.length > lineCountLimit)
+            return false;
+        
+        if (lineBreak == "explicit")
+        {
+            // if explicit, if the right edge of any lines go outside the
+            // desired width
+            if (bounds.right > width)
+                return false;
+        }
+
+        // No lines or one line or no height restriction.  We don't truncate away
+        // the one and only line just because height is too small.  Clipping
+        // will take care of it later
+        if (textLines.length <= 1 || isNaN(height))
+            return true;
+                                             
+        // Does the bottom of the last line fall within the bounds?                                                    
+        var lastLine:TextLine = TextLine(textLines[textLines.length - 1]);        
+        var lastLineExtent:Number = lastLine.y + lastLine.descent;
+        
+        return lastLineExtent <= height;
+    }
+
+    /**
+     *  @private
+     *  width and height are the ones used to do the compose, not the measured
+     *  results resulting from the compose.
+     * 
+     *  Adapted from justification code in TLF's
+     *  TextLineFactory.textLinesFromString().
+     */
+    private function truncateText(width:Number, height:Number, lineBreak:String):void
+    {
+        var lineCountLimit:int = maxDisplayedLines;
+        var somethingFit:Boolean = false;
+        var truncLineIndex:int = 0;    
+
+        if (lineBreak == "explicit")
+        {
+            truncateExplicitLineBreakText(width, height);
+            return;
+        }
+
+        // Compute the truncation line.
+        truncLineIndex = computeLastAllowedLineIndex(height, lineCountLimit);                                                     
+                                                     
+        if (truncLineIndex >= 0)
+        {
+            // Estimate the initial truncation position using the following 
+            // steps. 
+            
+            // 1. Measure the space that the truncation indicator will take
+            // by composing the truncation resource using the same bounds
+            // and formats.  The measured indicator lines could be cached but
+            // as well as being dependent on the indicator string, they are 
+            // dependent on the given width.            
+            staticTextElement.text = truncationIndicatorResource;
+            var indicatorLines:Vector.<DisplayObject> =
+                new Vector.<DisplayObject>();
+            var indicatorBounds:Rectangle = new Rectangle(0, 0, width, NaN);
+    
+            var indicatorFits:Boolean = createTextLinesFromTextBlock(staticTextBlock, 
+                                                                     indicatorLines, 
+                                                                     indicatorBounds);
+                                               
+            releaseLinesFromTextBlock();
+                                                                                                         
+            // 2. Move target line for truncation higher by as many lines 
+            // as the number of full lines taken by the truncation 
+            // indicator. Indicator should also be able to fit.
+            truncLineIndex -= (indicatorLines.length - 1);
+            if (truncLineIndex >= 0 && indicatorFits)
+            {
+                // 3. Calculate allowed width (width left over from the 
+                // last line of the truncation indicator).
+                var measuredTextLine:TextLine = 
+                    TextLine(indicatorLines[indicatorLines.length - 1]);      
+                var allowedWidth:Number = 
+                    measuredTextLine.specifiedWidth -
+                    measuredTextLine.unjustifiedTextWidth;                          
+
+                measuredTextLine = null;                                        
+                releaseTextLines(indicatorLines);
+                               
+                // 3b. Add extra line in case we wordwrapped some characters onto extra lines.  
+                // If we truncate in the middle of the last word it may then fit on the line above.
+                // For something like this, '-shgfhhf-4spphngdfgn_sg', the word breaks are at the
+                // '-' so several there are several text lines.
+                // If there is another text line past the one we are targeting for truncation,
+                // consider it. Calculate the width available on the line that will be truncated
+                // for characters from the next, extra line.
+                // (width of last line - width of text on last line - width of indicator) is
+                // the width available on the truncated line for text from the next, extra line.
+                if (truncLineIndex + 1 < textLines.length)
+                {
+                    const lastLine:TextLine = TextLine(textLines[truncLineIndex]);
+                    allowedWidth -= lastLine.unjustifiedTextWidth;
+                    // the next, extra line.
+                    truncLineIndex++;
+                }
+                
+                // 4. Get the initial truncation position on the target 
+                // line given this allowed width.
+                var truncateAtCharPosition:int = getTruncationPosition(
+                    TextLine(textLines[truncLineIndex]), allowedWidth);
+
+                // The following loop executes repeatedly composing text until 
+                // it fits.  In each iteration, an atoms's worth of characters 
+                // of original content is dropped
+                do
+                {
+                    // Replace all content starting at the inital truncation 
+                    // position with the truncation indicator.
+                    var truncText:String = text.slice(0, truncateAtCharPosition) +
+                                           truncationIndicatorResource;
+
+                    // (Re)-initialize bounds for next compose.
+                    bounds.x = 0;
+                    bounds.y = 0;
+                    bounds.width = width;
+                    bounds.height = height;
+                                                                                    
+                    staticTextElement.text = truncText;
+                    
+                    var createdAllLines:Boolean = createTextLinesFromTextBlock(
+                        staticTextBlock, textLines, bounds);
+        
+                    if (doesComposedTextFit(height, width,
+                                            createdAllLines, 
+                                            lineCountLimit, lineBreak))
+
+                    {
+                        somethingFit = true;
+                        break; 
+                    }       
+                    
+                     // No original content left to make room for 
+                     // truncation indicator.
+                    if (truncateAtCharPosition == 0)
+                        break;
+                    
+                    // Try again by truncating at the beginning of the 
+                    // preceding atom.
+                    var oldCharPosition:int = truncateAtCharPosition;
+                    truncateAtCharPosition = getNextTruncationPosition(
+                        truncLineIndex, truncateAtCharPosition);  
+                    // check to see if we've run out of chars
+                    if (oldCharPosition == truncateAtCharPosition)
+                        break;
+                }
+                while (true);
+            }
+        }
+
+        // If nothing fit, return no lines and bounds that just contains
+        // padding.
+        if (!somethingFit)
+        {
+            releaseTextLines();
+
+            var paddingBottom:Number = getStyle("paddingBottom");
+            var paddingLeft:Number = getStyle("paddingLeft");
+            var paddingRight:Number = getStyle("paddingRight");
+            var paddingTop:Number = getStyle("paddingTop");
+            
+            bounds.x = 0;
+            bounds.y = 0;
+            bounds.width = paddingLeft + paddingRight;
+            bounds.height = paddingTop + paddingBottom;
+        }
+        
+        // The text was truncated.
+        setIsTruncated(true);
+    }
+        
+    /**
+     *  @private
+     *  width and height are the ones used to do the compose, not the measured
+     *  results resulting from the compose.
+     */
+    private function truncateExplicitLineBreakText(width:Number, height:Number):void
+    {
+        // 1. Measure the space that the truncation indicator will take
+        // by composing the truncation resource using the same bounds
+        // and formats.  The measured indicator lines could be cached but
+        // as well as being dependent on the indicator string, they are 
+        // dependent on the given width.            
+        staticTextElement.text = truncationIndicatorResource;
+        var indicatorLines:Vector.<DisplayObject> =
+            new Vector.<DisplayObject>();
+        var indicatorBounds:Rectangle = new Rectangle(0, 0, width, NaN);
+
+        createTextLinesFromTextBlock(staticTextBlock, 
+                                     indicatorLines, 
+                                     indicatorBounds);
+                                           
+        releaseLinesFromTextBlock();
+
+        // check each line to see if it needs truncation
+        var n:int = textLines.length;
+        for (var i:int = 0; i < n; i++)
+        {
+            var line:TextLine = textLines[i] as TextLine;
+            // if the line is wider than bounds or off the left side
+            // TODO (aharui): What if text runs off left side because of
+            // alignment or direction?
+            if ((line.x + line.width) > width)
+            {
+                // clip this line
+                var lineLength:int = line.rawTextLength;
+                // start chopping from the end until it fits
+                while (--lineLength > 0)
+                {
+                    var lineStr:String = text.substr(line.textBlockBeginIndex, lineLength);
+                    lineStr += truncationIndicatorResource;
+                    staticTextElement.text = lineStr;
+                    var clippedLines:Vector.<DisplayObject> =
+                        new Vector.<DisplayObject>();
+
+                    createTextLinesFromTextBlock(staticTextBlock, 
+                                                 clippedLines, 
+                                                 indicatorBounds);
+                                           
+                    releaseLinesFromTextBlock();
+                    if (clippedLines.length == 1 && 
+                        (clippedLines[0].x + clippedLines[0].width) <= width)
+                    {
+                        // replace with the clipped line
+                        clippedLines[0].x = line.x;
+                        clippedLines[0].y = line.y;
+                        textLines[i] = clippedLines[0];
+                        break;
+                    }
+                    
+                }
+            }
+        }
+    }
+
+    /** 
+     *  @private
+     *  Calculates the last line that fits in the given height and line count 
+     *  limit.
+     */
+    private function computeLastAllowedLineIndex(height:Number,
+                                                 lineCountLimit:int):int
+    {           
+        var truncationLineIndex:int = textLines.length - 1;
+        // return -1 if no textLines (usually because zero size)
+        if (truncationLineIndex < 0)
+            return truncationLineIndex;
+        
+        if (!isNaN(height))
+        {
+            // Search in reverse order since truncation near the end is the 
+            // more common use case.
+            do
+            {
+                var textLine:TextLine = TextLine(textLines[truncationLineIndex]);
+                if (textLine.y + textLine.descent <= height)
+                    break;
+                                
+                truncationLineIndex--;
+            }
+            while (truncationLineIndex >= 0);
+        }   
+    
+        // if line count limit is smaller, use that
+        if (lineCountLimit != -1 && lineCountLimit <= truncationLineIndex)
+            truncationLineIndex = lineCountLimit - 1;            
+            
+        return truncationLineIndex;            
+    }
+
+    /** 
+     *  @private
+     *  Gets the initial truncation position on a line.
+     * 
+     *  If there is an extra line, start at the first word boundary since
+     *  truncating characters in this word may make it fit on the line above.
+     * 
+     *  If there is not an extra line, start at the allowed width.
+     * 
+     *  - Must be at an atom boundary.
+     *  - Must scan the line for atoms in logical order, not physical position 
+     *    order.
+     *  For example, given bi-di text ABאבCD
+     *  atoms must be scanned in this order: 
+     *  A, B, א
+     *  ג, C, D  
+     */
+    private function getTruncationPosition(line:TextLine, 
+                                           allowedWidth:Number):int
+    {           
+        var consumedWidth:Number = 0;
+        var charPosition:int = line.textBlockBeginIndex;
+        
+        while (charPosition < line.textBlockBeginIndex + line.rawTextLength)
+        {
+            var atomIndex:int = line.getAtomIndexAtCharIndex(charPosition);
+            var atomBounds:Rectangle = line.getAtomBounds(atomIndex); 
+            consumedWidth += atomBounds.width;
+            if (consumedWidth > allowedWidth)
+                break;
+
+            charPosition = line.getAtomTextBlockEndIndex(atomIndex);
+        }
+        
+        return charPosition;
+    }
+       
+    /** 
+     *  @private
+     *  Gets the next truncation position by shedding an atom's worth of 
+     *  characters.
+     */
+    private function getNextTruncationPosition(truncationLineIndex:int,
+                                               truncateAtCharPosition:int):int
+    {
+        // 1. Get the position of the last character of the preceding atom
+        // truncateAtCharPosition-1, because truncateAtCharPosition is an 
+        // atom boundary.
+        truncateAtCharPosition--; 
+        
+        // 2. Find the new target line (i.e., the line that has the new 
+        // truncation position).  If the last truncation position was at the 
+        // beginning of the target line, the new position may have moved to a 
+        // previous line.  It is also possible for this position to be found 
+        // in the next line because the truncation indicator may have combined 
+        // with original content to form a word that may not have afforded a 
+        // suitable break opportunity.  In any case, the new truncation 
+        // position lies in the vicinity of the previous target line, so a 
+        // linear search suffices.
+        var line:TextLine = TextLine(textLines[truncationLineIndex]);
+        do
+        {
+            if (truncateAtCharPosition >= line.textBlockBeginIndex && 
+                truncateAtCharPosition < line.textBlockBeginIndex + line.rawTextLength)
+            {
+                break;
+            }
+            
+            if (truncateAtCharPosition < line.textBlockBeginIndex)
+            {
+                truncationLineIndex--;
+                // if we run out of chars, just return the same
+                // position to warn the caller to stop
+                if (truncationLineIndex < 0)
+                    return truncateAtCharPosition;
+            }
+            else
+            {
+                truncationLineIndex++;
+                // if we run out of chars, just return the same
+                // position to warn the caller to stop
+                if (truncationLineIndex >= textLines.length)
+                    return truncateAtCharPosition;
+            }
+                
+            line = TextLine(textLines[truncationLineIndex]);
+        }
+        while (true);
+
+        // 3. Get the line atom index at this position          
+        var atomIndex:int = 
+                        line.getAtomIndexAtCharIndex(truncateAtCharPosition);
+        
+        // 4. Get the char index for this atom index
+        var nextTruncationPosition:int = 
+                        line.getAtomTextBlockBeginIndex(atomIndex);
+                
+        return nextTruncationPosition;
+    }
+    
+    /**
+     *  @private
+     *  Cleans up and sets the validity of the lines associated 
+     *  with the TextBlock to TextLineValidity.INVALID.
+     */
+    private function releaseLinesFromTextBlock():void
+    {
+        var firstLine:TextLine = staticTextBlock.firstLine;
+        var lastLine:TextLine = staticTextBlock.lastLine;
+        
+        if (firstLine)
+            staticTextBlock.releaseLines(firstLine, lastLine);        
+     }
+}
+
+}

Propchange: incubator/flex/whiteboard/Label_Truncation_Fix-SDK-32119/Label.as
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/flex/whiteboard/Label_Truncation_Fix-SDK-32119/Label.as
------------------------------------------------------------------------------
    svn:mime-type = text/plain



Mime
View raw message