////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2008 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 spark.components
{

import flash.display.DisplayObject;
import flash.text.TextFormat;

import flashx.textLayout.compose.ISWFContext;
import flashx.textLayout.conversion.ConversionType;
import flashx.textLayout.conversion.ITextExporter;
import flashx.textLayout.conversion.ITextImporter;
import flashx.textLayout.conversion.TextConverter;
import flashx.textLayout.elements.Configuration;
import flashx.textLayout.elements.GlobalSettings;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.events.DamageEvent;
import flashx.textLayout.factory.StringTextLineFactory;
import flashx.textLayout.factory.TextFlowTextLineFactory;
import flashx.textLayout.factory.TextLineFactoryBase;
import flashx.textLayout.factory.TruncationOptions;
import flashx.textLayout.formats.ITextLayoutFormat;
import flashx.textLayout.tlf_internal;

import mx.core.IEmbeddedFontRegistry;
import mx.core.IFlexModuleFactory;
import mx.core.IUIComponent;
import mx.core.Singleton;
import mx.core.UIComponent;
import mx.core.mx_internal;

import spark.components.supportClasses.TextBase;
import spark.core.CSSTextLayoutFormat;
import spark.core.MaskType;
import spark.utils.MaskUtil;
import spark.utils.TextUtil;

use namespace mx_internal;
use namespace tlf_internal

//--------------------------------------
//  Styles
//--------------------------------------

include "../styles/metadata/BasicInheritingTextStyles.as"
include "../styles/metadata/BasicNonInheritingTextStyles.as"
include "../styles/metadata/AdvancedInheritingTextStyles.as"
include "../styles/metadata/AdvancedNonInheritingTextStyles.as"

//--------------------------------------
//  Other metadata
//--------------------------------------

[DefaultProperty("content")]

[IconFile("RichText.png")]

[DiscouragedForProfile("mobileDevice")]

/**
 *  RichText is a low-level UIComponent that can display one or more lines
 *  of richly-formatted text and embedded images.
 *
 *  <p>For performance reasons, it does not support scrolling,
 *  selection, editing, clickable hyperlinks, or images loaded from URLs.
 *  If you need those capabilities, please see the RichEditableText
 *  class.</p>
 *
 *  <p>RichText uses the Text Layout Framework (TLF) library, which in turn builds on
 *  the Flash Text Engine (FTE) in Flash Player 10.
 *  In combination, they provide rich text layout using
 *  high-quality international typography.</p>
 *
 *  <p>The Spark architecture provides three text "primitives" -- 
 *  Label, RichText, and RichEditableText.
 *  Label is the fastest and most lightweight
 *  because it uses only FTE, not TLF,
 *  but it is limited in its capabilities: no rich text,
 *  no scrolling, no selection, and no editing.
 *  RichText adds the ability to display rich text
 *  with complex layout, but is still completely non-interactive.
 *  RichEditableText is the heaviest-weight,
 *  but offers most of what TLF can do.
 *  You should use the lightest-weight text primitive that meets your needs.</p>
 *
 *  <p>RichText is similar to the MX control mx.controls.Text.
 *  The Text control uses the older TextField class, instead of TLF,
 *  to display text.</p>
 *
 *  <p>The most important differences between RichText and Text are:
 *  <ul>
 *    <li>RichText offers better typography, better support
 *        for international languages, and better text layout than Text.</li>
 *    <li>RichText has an object-oriented model of what it displays,
 *        while Text does not.</li>
 *    <li>Text is selectable, while RichText does not support selection.</li>
 *  </ul></p>
 *
 *  <p>RichText uses TLF's object-oriented model of rich text,
 *  in which text layout elements such as divisions, paragraphs, spans,
 *  and images are represented at runtime by ActionScript objects
 *  which can be programmatically accessed and manipulated.
 *  The central object in TLF for representing rich text is a
 *  TextFlow, and you specify what RichText should display
 *  by setting its <code>textFlow</code> property to a TextFlow instance.
 *  (Please see the description of the <code>textFlow</code>
 *  property for information about how to create one.)
 *  You can also set the <code>text</code> property that
 *  is inherited from TextBase, but if you don't need
 *  the richness of a TextFlow, you should consider using
 *  Label instead.</p>
 *
 *  <p>At compile time, you can put TLF markup tags inside
 *  the RichText tag, as the following example shows:
 *  <pre>
 *  &lt;s:RichText&gt;Hello &lt;s:span fontWeight="bold"&gt;World!&lt;/s:span&gt;&lt;/s:RichText&gt;
 *  </pre>
 *  In this case, the MXML compiler sets the <code>content</code>
 *  property, causing a TextFlow to be automatically created
 *  from the FlowElements that you specify.</p>
 *
 *  <p>The default text formatting is determined by CSS styles
 *  such as <code>fontFamily</code>, <code>fontSize</code>.
 *  Any formatting information in the TextFlow overrides
 *  the default formatting provided by the CSS styles.</p>
 *
 *  <p>You can control the spacing between lines with the
 *  <code>lineHeight</code> style and the spacing between
 *  paragraphs with the <code>paragraphSpaceBefore</code>
 *  and <code>paragraphSpaceAfter</code> styles.
 *  You can align or justify the text using the <code>textAlign</code>
 *  and <code>textAlignLast</code> styles.
 *  You can inset the text from the component's edges using the
 *  <code>paddingLeft</code>, <code>paddingTop</code>, 
 *  <code>paddingRight</code>, and <code>paddingBottom</code> styles.</p>
 *
 *  <p>If you don't specify any kind of width for a RichText,
 *  then the longest line, as determined by these explicit line breaks,
 *  determines the width of the Label.</p>
 *
 *  <p>When you specify a width, the text wraps at the right
 *  edge of the component and the text is clipped when there is more
 *  text than fits.
 *  If you set the <code>lineBreak</code> style to <code>explicit</code>,
 *  new lines will start only at explicit lines breaks, such as
 *  if you use CR (<code>\r</code>), LF (<code>\n</code>),
 *  or CR+LF (<code>\r\n</code>) in <code>text</code>
 *  or if you use <code>&lt;p&gt;</code> and <code>&lt;br/&gt;</code>
 *  in TLF markup. In that case, lines that are wider than the control
 *  are clipped.</p>
 *
 *  <p>If you have more text than you have room to display it,
 *  RichText 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>By default,RichText 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 RichText.</p>
 *
 *  <p>Because RichText uses TLF,
 *  it 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 one.
 *  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>RichText uses TLF's StringTextFlowFactory and TextFlowTextLineFactory
 *  classes to create one or more TextLine objects to statically display
 *  its text.
 *  For performance, its TextLines do not contain information
 *  about individual glyphs; for more info, see the TextLineValidity class.</p>
 *
 *  <p>To use this component in a list-based component, such as a List or DataGrid, 
 *  create an item renderer.
 *  For information about creating an item renderer, see 
 *  <a href="http://help.adobe.com/en_US/flex/using/WS4bebcd66a74275c3-fc6548e124e49b51c4-8000.html">
 *  Custom Spark item renderers</a>. </p>
 *  
 *  @mxml
 *
 *  <p>The <code>&lt;s:RichText&gt;</code> tag inherits all of the tag 
 *  attributes of its superclass and adds the following tag attributes:</p>
 *
 *  <pre>
 *  &lt;s:RichText
 *    <strong>Properties</strong>
 *    fontContext="<i>IFlexModuleFactory</i>"
 *    luminosityClip="false"
 *    luminosityInvert="false"
 *    maskType="MaskType.CLIP"
 *    textFlow="<i>TextFlow</i>"
 *  /&gt;
 *  </pre>
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 *
 *  @see spark.components.RichEditableText
 *  @see spark.components.Label
 *  @see flash.text.engine.TextLineValidity
 *  
 *  @includeExample examples/RichTextExample.mxml
 */
public class RichText extends TextBase
{
    include "../core/Version.as";

    //--------------------------------------------------------------------------
    //
    //  Class variables
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private static var classInitialized:Boolean = false;
    
    /**
     *  @private
     *  This TLF object composes TextLines from a text String.
     *  We use it when the 'text' property is set to a String
     *  that doesn't contain linebreaks.
     */
    private static var staticStringFactory:StringTextLineFactory;
    
    /**
     *  @private
     *  This TLF object composes TextLines from a TextFlow.
     *  We use it when the 'textFlow' or 'content' property is set,
     *  and when the 'text' property is set to a String
     *  that contains linebreaks (and therefore is interpreted
     *  as multiple paragraphs).
     */
    private static var staticTextFlowFactory:TextFlowTextLineFactory;
    
    /**
     *  @private
     *  This TLF object is used to import a 'text' String
     *  containing linebreaks to create a multiparagraph TextFlow.
     */
    private static var staticPlainTextImporter:ITextImporter;
    
    /**
     *  @private
     *  This TLF object is used to export a TextFlow as plain 'text',
     *  by walking the leaf FlowElements in the TextFlow.
     */
    private static var staticPlainTextExporter:ITextExporter;
    
    //--------------------------------------------------------------------------
    //
    //  Class methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  This method initializes the static vars of this class.
     *  Rather than calling it at static initialization time,
     *  we call it in the constructor to do the class initialization
     *  when the first instance is created.
     *  (It does an immediate return if it has already run.)
     *  By doing so, we avoid any static initialization issues
     *  related to whether this class or the TLF classes
     *  that it uses are initialized first.
     */
    private static function initClass():void
    {
        if (classInitialized)
            return;
            
        // Set the TLF hook used for localizing runtime error messages.
        // TLF itself has English-only messages,
        // but higher layers like Flex can provide localized versions.
        GlobalSettings.resourceStringFunction = TextUtil.getResourceString;

        // Set the TLF hook used to specify the callback used for changing 
        // the FontLookup based on SWFContext.  
        GlobalSettings.resolveFontLookupFunction = TextUtil.resolveFontLookup;

        // Pre-FP10.1, set default tab stops in TLF.  Without this, if there
        // is a tab and TLF is measuring width, the tab will
        // measure as the rest of the remaining width up to 10000.
        GlobalSettings.enableDefaultTabStops = !Configuration.playerEnablesArgoFeatures;
        
        staticStringFactory = new StringTextLineFactory();
        
        staticTextFlowFactory = new TextFlowTextLineFactory();
        
        staticPlainTextImporter =
            TextConverter.getImporter(TextConverter.PLAIN_TEXT_FORMAT);
        
        staticPlainTextExporter =
            TextConverter.getExporter(TextConverter.PLAIN_TEXT_FORMAT);
            
        classInitialized = true;
    }
    
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor. 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function RichText()
    {
        super();
        
        initClass();
        
        text = "";
    }
     
    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  This object determines the default text formatting used
     *  by this component, based on its CSS styles.
     *  It is set to null by stylesInitialized() and styleChanged(),
     *  and recreated whenever necessary in commitProperties().
     */
    private var hostFormat:ITextLayoutFormat;

    /**
     *  @private
     *  Holds the last recorded value of the textFlow generation for _textFlow.  
     *  Used to determine whether to return immediately from damage event if 
     *  there have been no changes.
     */
    private var lastGeneration:uint = 0;    // 0 means not set
        
    /**
     *  @private
     *  Holds the last recorded value of the module factory
     *  used to create the font.
     */
    mx_internal var embeddedFontContext:IFlexModuleFactory;
    
    /**
     *  @private
     *  Specifies whether the StringTextLineFactory
     *  or the TextFlowTextLineFactory is used to create the TextLines.
     *  A StringTextLineFactory is more efficient; it is used
     *  by default to render the default text ""
     *  and when 'text' is set to a string without linebreaks;
     *  otherwise, a TextFlowTextLineFactory is used.
     */
    private var factory:TextLineFactoryBase;

    /**
     *  @private
     *  If true, the damage handler will return immediately.
     */
    private var ignoreDamageEvent:Boolean;
    
    //--------------------------------------------------------------------------
    //
    //  Overridden properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  text
    //----------------------------------

    // Compiler will strip leading and trailing whitespace from text string.
    [CollapseWhiteSpace]
    
    // The _text storage var is mx_internal in TextBase.
    
    /**
     *  @private
     */
    private var textChanged:Boolean = false;
    
    /**
     *  @private
     *  Source of text: one of "text", "textFlow" or "content".* 
     */
    private var source:String = "";
    
    /**
     *  @private
     */
    override public function get text():String
    {
        // Extracting the plaintext from a TextFlow is somewhat expensive,
        // as it involves iterating over the leaf FlowElements in the TextFlow.
        // Therefore we do this extraction only when necessary, namely when
        // you first set the 'content' or the 'textFlow'
        // (or mutate the TextFlow), and then get the 'text'.
        if (_text == null)
        {
            // If 'content' was last set,
            // we have to first turn that into a TextFlow.
            if (contentChanged)
            {
                _textFlow = createTextFlowFromContent(_content);
                lastGeneration = _textFlow.generation;
            }
                    
            // Once we have a TextFlow, we can export its plain text.
            _text = staticPlainTextExporter.export(
                _textFlow, ConversionType.STRING_TYPE) as String;
        }

        return _text;
    }

    /**
     *  @private
     *  This will create a TextFlow with a single paragraph with a single span 
     *  with exactly the text specified.  If there is whitespace and line 
     *  breaks in the text, they will remain, regardless of the settings of
     *  the lineBreak and whiteSpaceCollapse styles.
     */
    override public function set text(value:String):void
    {
        // Treat setting the 'text' to null
        // as if it were set to the empty String
        // (which is the default state).
        if (value == null)
            value = "";
        
        // If the most recent change to _text was caused by calling this method,
        // then it's safe to short-cicuit in the same way that TextBase does.
        if ((source == "text") && (value == _text))
            return;
        
        // Don't return early if value is the same as _text,
        // because _text might have been produced from setting
        // 'textFlow' or 'content'.
        // For example, if you set a TextFlow corresponding to
        // "Hello <span color="OxFF0000">World</span>"
        // and then get the 'text', it will be the String "Hello World"
        // But if you then set the 'text' to "Hello World"
        // this represents a change: the "World" should no longer be red.
        
        _text = value;
        textChanged = true;
        source = "text";
        
        // If more than one of 'text', 'textFlow', and 'content' is set,
        // the last one set wins.
        textFlowChanged = false;
        contentChanged = false;
        
        // If there was a textFlow remove its damage handler.
        removeDamageHandler();

        // The other two are now invalid and must be recalculated when needed.
        _textFlow = null;
        _content = null;
        
        factory = staticStringFactory;
        
        invalidateTextLines();
        invalidateProperties();
        invalidateSize();
        invalidateDisplayList();
    }
    
    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  content
    //----------------------------------

    /**
     *  @private
     *  Storage for the content property.
     */
    protected var _content:Object;
    
    /**
     *  @private
     */
    private var contentChanged:Boolean = false;
    
    /**
     *  @private
     *  This metadata tells the MXML compiler to disable some of its default
     *  interpretation of the value specified for the 'content' property.
     *  Normally, for properties of type Object, it assumes that things
     *  looking like numbers are numbers and things looking like arrays
     *  are arrays. But <content>1</content> should generate code to set the
     *  content to  the String "1", not the int 1, and <content>[1]</content>
     *  should set it to the String "[1]", not the Array [ 1 ].
     *  However, {...} continues to be interpreted as a databinding
     *  expression, and @Resource(...), @Embed(...), etc.
     *  as compiler directives.
     *  Similar metadata on TLF classes causes the same rules to apply
     *  within <p>, <span>, etc.
     */
    [RichTextContent]
    
    /**
     *  This property is intended for use in MXML at compile time;
     *  to get or set rich text content at runtime,
     *  please use the <code>textFlow</code> property instead.
     *
     *  <p>The <code>content</code> property is the default property
     *  for RichText, so that you can write MXML such as
     *  <pre>
     *  &lt;s:RichText&gt;Hello &lt;s:span fontWeight="bold"/&gt;World&lt;/s:span&gt;&lt;/s:RichText&gt;
     *  </pre>
     *  and have the String and SpanElement that you specify
     *  as the content be used to create a TextFlow.</p>
     *
     *  <p>This property is typed as Object because you can set it to
     *  to a String, a FlowElement, or an Array of Strings and FlowElements.
     *  In the example above, you are specifying the content
     *  to be a 2-element Array whose first element is the String
     *  "Hello" and whose second element is a SpanElement with the text
     *  "World" in boldface.</p>
     * 
     *  <p>No matter how you specify the content, it gets converted
     *  into a TextFlow, and when you get this property, you will get
     *  the resulting TextFlow.</p>
     * 
     *  <p>Adobe recommends using <code>textFlow</code> property
     *  to get and set rich text content at runtime,
     *  because it is strongly typed as a TextFlow
     *  rather than as an Object.
     *  A TextFlow is the canonical representation
     *  for rich text content in the Text Layout Framework.</p>
     * 
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get content():Object
    {
        return textFlow;
    }
    
    /**
     *  @private
     */   
    public function set content(value:Object):void
    {
        // Treat setting the 'content' to null
        // as if 'text' were being set to the empty String
        // (which is the default state).
        if (value == null)
        {
            text = "";
            return;
        }
        
        if (value == _content)
            return;
        
        _content = value;
        contentChanged = true;
        source = "content";
        
        // If more than one of 'text', 'textFlow', and 'content' is set,
        // the last one set wins.
        textChanged = false;
        textFlowChanged = false;
        
        // If there was a textFlow remove its damage handler.
        removeDamageHandler();

        // The other two are now invalid and must be recalculated when needed.
        _text = null;
        _textFlow = null;
                
        factory = staticTextFlowFactory;
        
        invalidateTextLines();
        invalidateProperties();
        invalidateSize();
        invalidateDisplayList();
    }
    
    //----------------------------------
    //  mask
    //----------------------------------
    
    /**
     *  @private
     */
    private var maskChanged:Boolean;
       
    /**
     *  @private
     */
    override public function set mask(value:DisplayObject):void
    {
        if (super.mask == value)
            return;
        
        var oldMask:UIComponent = super.mask as UIComponent;
        
        super.mask = value;      
                
        // If the old mask was attached by us, then we need to 
        // undo the attachment logic        
        if (oldMask && oldMask.$parent === this)
        {       
            if (oldMask.parent is UIComponent)
                UIComponent(oldMask.parent).childRemoved(oldMask);
            oldMask.$parent.removeChild(oldMask);
        }     
                
        maskChanged = true;
        maskTypeChanged = true;
                
        invalidateProperties();
        invalidateDisplayList();
    }
    
    //----------------------------------
    //  maskType
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the maskType property.
     */
    private var _maskType:String = MaskType.CLIP;
    
    /**
     *  @private
     */
    private var maskTypeChanged:Boolean;
    
    [Inspectable(defaultValue="clip", enumeration="clip,alpha,luminosity")]

    /**
     *  <p>The maskType defines how the mask is applied to the component.</p> 
     * 
     *  <p>The possible values are <code>MaskType.CLIP</code>, <code>MaskType.ALPHA</code> and 
     *  <code>MaskType.LUMINOSITY</code>.</p>  
     * 
     *  <p><strong>Clip Masking</strong></p>
     * 
     *  <p>When masking in clip mode, a clipping masks is reduced to 1-bit.  This means that a mask will 
     *  not affect the opacity of a pixel in the source content; it either leaves the value unmodified, 
     *  if the corresponding pixel in the mask is has a non-zero alpha value, or makes it fully 
     *  transparent, if the mask pixel value has an alpha value of zero.</p>
     * 
     *  <p>When clip masking is used, only the actual path and shape vectors and fills defined by the
     *  mask are used to determine the effect on the source content.  strokes and bitmap filters 
     *  defined on the mask are ignored.  Any filled region in the mask is considered filled, and renders 
     *  the source content.  The type and parameters of the fill is irrelevant;  a solid color fill, 
     *  gradient fill, or bitmap fill in a mask will all render the underlying source content, regardless 
     *  of the alpha values of the mask fill.</p>
     *  
     *  <p>BitmapGraphics are treated as bitmap filled rectangles when used in a clipping mask.  As a 
     *  result, the alpha channel of the source bitmap is irrelevant when part of a mask -- the bitmap 
     *  affects the mask in the same manner as solid filled rectangle of equivalent dimensions.</p>
     * 
     *  <p><strong>Alpha Masking</strong></p>
     * 
     *  <p>In alpha mode, the opacity of each pixel in the source content is multiplied by the opacity 
     *  of the corresponding region of the mask.  i.e., a pixel in the source content with an opacity of 
     *  1 that is masked by a region of opacity of .5 will have a resulting opacity of .5.  A source pixel 
     *  with an opacity of .8 masked by a region with opacity of .5 will have a resulting opacity of .4.</p>
     * 
     *  <p>Conceptually, alpha masking is equivalent to rendering the transformed mask and source content 
     *  into separate RGBA surfaces, and multiplying the alpha channel of the mask content into the alpha 
     *  channel of the source content.  All of the mask content is rendered into its surface before 
     *  compositing into the source content's surface. As a result, all FXG features, such as strokes, 
     *  bitmap filters, and fill opacity will affect the final composited content.</p>
     * 
     *  <p>When in alpha mode, the alpha channel of any bitmap data is composited normally into the mask 
     *  alpha channel, and will affect the final rendered content. This holds true for both BitmapGraphics 
     *  and bitmap filled shapes and paths.</p>
     * 
     *  <p><strong>Luminosity Masking</strong></p>
     * 
     *  <p>A luminosity mask, sometimes called a 'soft mask', works very similarly to an alpha mask
     *  except that both the opacity and RGB color value of a pixel in the source content is multiplied
     *  by the opacity and RGB color value of the corresponding region in the mask.</p>
     * 
     *  <p>Conceptually, luminosity masking is equivalent to rendering the transformed mask and source content 
     *  into separate RGBA surfaces, and multiplying the alpha channel and the RGB color value of the mask 
     *  content into the alpha channel and RGB color value of the source content.  All of the mask content is 
     *  rendered into its surface before compositing into the source content's surface. As a result, all FXG 
     *  features, such as strokes, bitmap filters, and fill opacity will affect the final composited 
     *  content.</p>
     * 
     *  <p>Luminosity masking is not native to Flash but is common in Adobe Creative Suite tools like Adobe 
     *  Illustrator and Adobe Photoshop. In order to accomplish the visual effect of a luminosity mask in 
     *  Flash-rendered content, a graphic element specifying a luminosity mask actually instantiates a shader
     *  filter that mimics the visual look of a luminosity mask as rendered in Adobe Creative Suite tools.</p>
     * 
     *  <p>Objects being masked by luminosity masks can set properties to control the RGB color value and 
     *  clipping of the mask. See the luminosityInvert and luminosityClip attributes.</p>
     * 
     *  @see spark.core.MaskType
     * 
     *  @default MaskType.CLIP
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get maskType():String
    {
        return _maskType;
    }
    
    /**
     *  @private
     */
    public function set maskType(value:String):void
    {
        if (_maskType == value)
            return;
        
        _maskType = value;
        maskTypeChanged = true;
        invalidateProperties();
    }
    
    //----------------------------------
    //  luminosityInvert
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the luminosityInvert property.
     */
    private var _luminosityInvert:Boolean = false; 
    
    /**
     *  @private
     */
    private var luminositySettingsChanged:Boolean;

    [Inspectable(defaultValue="false")]

    /**
     *  A property that controls the calculation of the RGB 
     *  color value of a graphic element being masked by 
     *  a luminosity mask. If true, the RGB color value of a  
     *  pixel in the source content is inverted and multipled  
     *  by the corresponding region in the mask. If false, 
     *  the source content's pixel's RGB color value is used 
     *  directly. 
     * 
     *  @default false 
     *  @see #maskType 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get luminosityInvert():Boolean
    {
        return _luminosityInvert;
    }
    
    /**
     *  @private
     */
    public function set luminosityInvert(value:Boolean):void
    {
        if (_luminosityInvert == value)
            return;
        
        _luminosityInvert = value;
        luminositySettingsChanged = true; 
    }
    
    //----------------------------------
    //  luminosityClip
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the luminosityClip property.
     */
    private var _luminosityClip:Boolean = false; 
        
    [Inspectable(defaultValue="false")]

    /**
     *  A property that controls whether the luminosity 
     *  mask clips the masked content. This property can 
     *  only have an effect if the graphic element has a 
     *  mask applied to it that is of type 
     *  <code>MaskType.LUMINOSITY</code>.  
     * 
     *  @default false 
     *  @see #maskType 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get luminosityClip():Boolean
    {
        return _luminosityClip;
    }
    
    /**
     *  @private
     */
    public function set luminosityClip(value:Boolean):void
    {
        if (_luminosityClip == value)
            return;
        
        _luminosityClip = value;
        luminositySettingsChanged = true; 
    }
    
    //----------------------------------
    //  textFlow
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the textFlow property.
     */
    private var _textFlow:TextFlow;
    
    /**
     *  @private
     */
    private var textFlowChanged:Boolean = false;
    
    /**
     *  The TextFlow representing the rich text displayed by this component.
     * 
     *  <p>A TextFlow is the most important class
     *  in the Text Layout Framework (TLF).
     *  It is the root of a tree of FlowElements
     *  representing rich text content.</p>
     *
     *  <p>You normally create a TextFlow from TLF markup
     *  using the <code>TextFlowUtil.importFromString()</code>
     *  or <code>TextFlowUtil.importFromXML()</code> methods.
     *  Alternately, you can use TLF's TextConverter class
     *  (which can import a subset of HTML) or build a TextFlow
     *  using methods like <code>addChild()</code> on TextFlow.</p>
     * 
     *  <p>Setting this property affects the <code>text</code> property
     *  and vice versa.</p>
     *
     *  <p>If you set the <code>textFlow</code> and get the <code>text</code>,
     *  the text in each paragraph will be separated by a single
     *  LF (<code>\n</code>).</p>
     *
     *  <p>If you set the <code>text</code> to a String such as
     *  <code>"Hello World"</code> and get the <code>textFlow</code>,
     *  it will be a TextFlow containing a single ParagraphElement
     *  with a single SpanElement.</p>
     *
     *  <p>If the text contains explicit line breaks --
     *  CR (<code>\r</code>), LF (<code>\n</code>), or CR+LF (<code>\r\n</code>) --
     *  then the content will be set to a TextFlow
     *  which contains multiple paragraphs, each with one span.</p>
     *
     *  <p>To turn a TextFlow object into TLF markup,
     *  use the markup returned from the <code>TextFlowUtil.export()</code> method.</p>
     *
     *  @see spark.utils.TextFlowUtil#importFromString()
     *  @see spark.utils.TextFlowUtil#importFromXML()
     *  @see spark.components.RichEditableText#text
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.5
     *  @productversion Flex 4.5
     */
    public function get textFlow():TextFlow
    {
        // We might not have a valid _textFlow for two reasons:
        // either because the 'text' was set (which is the state
        // after construction) or because the 'content' was set.
        if (!_textFlow)
        {
            if (_content != null)
                _textFlow = createTextFlowFromContent(_content);
            else
                _textFlow = staticPlainTextImporter.importToFlow(_text);
            
            lastGeneration = _textFlow ? _textFlow.generation : 0;
        }
        
        _textFlow.addEventListener(DamageEvent.DAMAGE,
                                   textFlow_damageHandler);
              
        // Ensure our textFlow has the most appropriate
        // swf context associated with it.                           
        if (_textFlow.flowComposer)
        {
            _textFlow.flowComposer.swfContext = 
                ISWFContext(getEmbeddedFontContext());  
        }                    
        
        return _textFlow;
    }
    
    /**
     *  @private
     */
    public function set textFlow(value:TextFlow):void
    {
        // Treat setting the 'textFlow' to null
        // as if 'text' were being set to the empty String
        // (which is the default state).
        if (value == null)
        {
            text = "";
            return;
        }
        
        if (value == _textFlow)
            return;
                    
        // If there was a textFlow remove its damage handler.
        removeDamageHandler();
        
        _textFlow = value;
        textFlowChanged = true;
        source = "textFlow";
        
        // If more than one of 'text', 'textFlow', and 'content' is set,
        // the last one set wins.
        textChanged = false;
        contentChanged = false;
        
        // The other two are now invalid and must be recalculated when needed.
        _text = null
        _content = null;
        
        factory = staticTextFlowFactory;

        invalidateTextLines();
        invalidateProperties();
        invalidateSize();
        invalidateDisplayList();
    }
    
    //--------------------------------------------------------------------------
    //
    //  Overridden methods: UIComponent
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     */
    override protected function commitProperties():void
    {
        super.commitProperties();
        
        // Only one of textChanged, textFlowChanged, and contentChanged
        // will be true; the other two will be false because each setter
        // guarantees this.
        if (textChanged)
        {
            // If the text has linebreaks (CR, LF, or CF+LF)
            // create a multi-paragraph TextFlow from it
            // and use the TextFlowTextLineFactory to render it.
            // Otherwise the StringTextLineFactory will put
            // all of the lines into a single paragraph
            // and FTE performance will degrade on a large paragraph.
            if (_text.indexOf("\n") != -1 || _text.indexOf("\r") != -1)
            {
                _textFlow = staticPlainTextImporter.importToFlow(_text);
                factory = staticTextFlowFactory;
            }
            textChanged = false;
        }
        else if (textFlowChanged)
        {
            // Nothing to do at commitProperties() time.
            textFlowChanged = false;
        }
        else if (contentChanged)
        {
            _textFlow = createTextFlowFromContent(_content);
            contentChanged = false;
        }
    
        lastGeneration = _textFlow ? _textFlow.generation : 0;
        
        // At this point we know which TextLineFactory we're going to use
        // and we know the _text or _textFlow that it will compose.
                    
        // If the styles have changed, hostFormat will have
        // been set to null to indicate that it is invalid.
        // In that case, create a new one.
        if (!hostFormat)
        {
            hostFormat = new CSSTextLayoutFormat(this);
            // Note: CSSTextLayoutFormat has special processing
            // for the fontLookup style. If it is "auto",
            // the fontLookup format is set to either
            // "device" or "embedded" depending on whether
            // embeddedFontContext is null or non-null.
        }
        
        if (_textFlow)
        {
            // We might have a new TextFlow, or a new hostFormat,
            // so attach the latter to the former.
            _textFlow.hostFormat = hostFormat;
    
            // Add a damage handler.
            _textFlow.addEventListener(DamageEvent.DAMAGE, 
                                       textFlow_damageHandler);
        }
        
        if (maskChanged)
        {
            if (mask && !mask.parent)
            {
                addChild(mask);
                MaskUtil.applyMask(mask, null);
            }
            maskChanged = false;            
        }        
        
        if (luminositySettingsChanged)
        {
            MaskUtil.applyLuminositySettings(
                mask, _maskType, _luminosityInvert, _luminosityClip);

            luminositySettingsChanged = false;             
        }

        if (maskTypeChanged)
        {
            MaskUtil.applyMaskType(
                mask, _maskType, _luminosityInvert, _luminosityClip, this);

            maskTypeChanged = false;
        }
    }
    
    /**
     *  @private
     */
    override public function stylesInitialized():void
    {
        super.stylesInitialized();

        // The old hostFormat is invalid
        // and a new one must be created.
        hostFormat = null;
    }

    /**
     *  @private
     */
    override public function styleChanged(styleProp:String):void
    {
        super.styleChanged(styleProp);

        // The old hostFormat is invalid
        // and a new one must be created.
        hostFormat = null;
        
        invalidateTextLines();
        invalidateProperties();
        invalidateSize();
        invalidateDisplayList();
    }

    /**
     *  @private
     */
    override protected function updateDisplayList(unscaledWidth:Number, 
                                                  unscaledHeight:Number):void
    {
        // The factory will compose just enough lines to fill the 
        // compositionHeight.  If not all the text is composed, the reported
        // contentHeight will be an estimate of what the height will be when
        // it is all composed and there will not be textLines to back it up.
        // There is no easy way to tell if the contentHeight is the actual
        // value or an estimated value.
        if (!isNaN(_composeHeight) && unscaledHeight != _composeHeight)
        {
            invalidateTextLines();
        }

        super.updateDisplayList(unscaledWidth, unscaledHeight);
    }    
    //--------------------------------------------------------------------------
    //
    //  Overridden methods: TextBase
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  Returns true to indicate all lines were composed.
     */
    override mx_internal function composeTextLines(width:Number = NaN,
                                                   height:Number = NaN):Boolean
    {   
        // If there is no explicit width but there is an explicit
        // maxWidth use that.
        if (isNaN(width) && !isNaN(explicitMaxWidth))
            width = explicitMaxWidth;
        
        super.composeTextLines(width, height);
        
        // Ignore damage events while we're re-composing the text lines.
        ignoreDamageEvent = true;

        // Set the composition bounds to be used by createTextLines().
        // If there is no explicit width, and there is no explicit maxWidth,
        // the width will be computed by this method.
        // If the 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;
        
        removeTextLines();
        releaseTextLines();
        
        createTextLines();
        
        // Truncation only done if not measuring width and line breaks are
        // toFit.  So if we are measuring, create the text lines to figure
        // out their size and then recreate them using this size so truncation 
        // will be done.
        if (maxDisplayedLines != 0 && !isTruncated &&
            getStyle("lineBreak") == "toFit")
        {
            var bp:String = getStyle("blockProgression");
            if ((isNaN(width) && bp == "tb") || (isNaN(height) && bp != "tb"))
            {
                textLines.length = 0;
                // bounds contains the measured size of the lines created above
                createTextLines();
            }
        }
        
        // Add the new text lines to the container.
        addTextLines();
        
        // Figure out if the text overruns the available space for composition.
        isOverset = isTextOverset(width, height);
        
        // Just recomposed so reset.
        invalidateCompose = false;
        
        // Listen for "damage" events in case the textFlow is 
        // modified programatically.
        ignoreDamageEvent = false;
        
        // Created all lines.
        return true;      
    }
    
    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private function createTextFlowFromContent(content:Object):TextFlow
    {
        var textFlow:TextFlow ;
        
        if (content is TextFlow)
        {
            textFlow = content as TextFlow;
        }
        else if (content is Array)
        {
            textFlow = new TextFlow();
            textFlow.whiteSpaceCollapse = getStyle("whiteSpaceCollapse");
            textFlow.mxmlChildren = content as Array;
            textFlow.whiteSpaceCollapse = undefined;
        }
        else
        {
            textFlow = new TextFlow();
            textFlow.whiteSpaceCollapse = getStyle("whiteSpaceCollapse");
            textFlow.mxmlChildren = [ content ];
            textFlow.whiteSpaceCollapse = undefined;
        }
        
        return textFlow;
    }
    
    /**
     *  @private
     *  Uses TextLineFactory to compose the textFlow
     *  into as many TextLines as fit into the bounds.
     */
    private function createTextLines():void
    {
        // Clear any previously generated TextLines from the textLines Array.
        textLines.length = 0;
        
        // Note: Even if we have nothing to compose, we nevertheless
        // use the StringTextLineFactory to compose an empty string.
        // Since it appends the paragraph terminator "\u2029",
        // it actually creates and measures one TextLine.
        // Its width is 0 but its height is equal to the font's
        // ascent plus descent.
        
        factory.compositionBounds = bounds;   
        
        // Set up the truncation options.
        var truncationOptions:TruncationOptions;
        if (maxDisplayedLines != 0)
        {
            truncationOptions = new TruncationOptions();
            truncationOptions.lineCountLimit = maxDisplayedLines;
            truncationOptions.truncationIndicator =
                TextBase.truncationIndicatorResource;
        }        
        factory.truncationOptions = truncationOptions;
        
        // 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();
       
        if (factory is StringTextLineFactory)
        {
            // We know text is non-null since it got this far.
            staticStringFactory.text = _text;
            staticStringFactory.textFlowFormat = hostFormat;
            staticStringFactory.swfContext = ISWFContext(embeddedFontContext);
            staticStringFactory.createTextLines(addTextLine);
        }
        else if (factory is TextFlowTextLineFactory)
        {
            if (_textFlow && _textFlow.flowComposer)
            {
                _textFlow.flowComposer.swfContext = 
                    ISWFContext(embeddedFontContext);
            }
            
            staticTextFlowFactory.swfContext = ISWFContext(embeddedFontContext);
            staticTextFlowFactory.createTextLines(addTextLine, _textFlow);
        }
        
        bounds = factory.getContentBounds();
        setIsTruncated(factory.isTruncated);
    }

    /**
     *  @private
     *  Uses StringTextLineFactory to compose an empty string so we can 
     *  determine the baseline from the text line.  The height is important
     *  if the text is vertically aligned.
     */
    override mx_internal function createEmptyTextLine(height:Number=NaN):void

    {
        // Clear any previously generated TextLines from the textLines Array.
        textLines.length = 0;
        
        // Note: 
        // Use the StringTextLineFactory to compose an empty string.
        // Since it appends the paragraph terminator "\u2029",
        // it actually creates and measures one TextLine.
        // Its width is 0 but its height is equal to the font's
        // ascent plus descent.
        // Note:  Prior to TLF2, the factory would callback addTextLine 
        // when width=0 but that has been optimized out.  Now width to NaN.
        
        bounds.x = 0;
        bounds.y = 0;
        bounds.width = width ? width : NaN;
        bounds.height = height;
        
        staticStringFactory.compositionBounds = bounds;   
        
        // Set up the truncation options.
        staticStringFactory.truncationOptions = null;
        
        staticStringFactory.text = "";
        staticStringFactory.textFlowFormat = hostFormat;
        staticStringFactory.createTextLines(addTextLine);
    }

    /**
     *  @private
     *  Callback passed to createTextLines().
     */
    private function addTextLine(textLine:DisplayObject):void
    {
        textLines.push(textLine);
    }

    /**
     *  @private
     *  Make sure to remove the damage handler before resetting the text flow.
     */
    private function removeDamageHandler():void
    {
        // Could check factory is TextFlowTextLineFactory but be safe and 
        // try to remove whenever there is a text flow.
        if (_textFlow != null)
        {
            _textFlow.removeEventListener(DamageEvent.DAMAGE,
                textFlow_damageHandler);
        }
    }

    //--------------------------------------------------------------------------
    //
    //  Event handlers
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  Called when the TextFlow dispatches a 'damage' event
     *  to indicate it has been modified.  This could mean the styles changed
     *  or the content changed, or both changed.
     */
    private function textFlow_damageHandler(event:DamageEvent):void
    {
        // If there are no changes to the generation, don't recompose.  
        // The TextFlowFactory createTextLines dispatches damage events every 
        // time the textFlow is composed, even if there are no changes.
        if (ignoreDamageEvent || _textFlow.generation == lastGeneration)
            return;
        
        // Update the last know generation for _textFlow.
        lastGeneration = _textFlow.generation;

        // Invalidate _text and _content.
        _text = null;
        _content = null;
        
        // After the TextFlow has been mutated,
        // we must render it, not the 'text' String.
        factory = staticTextFlowFactory;
        
        // Force recompose since text and/or styles may have changed.
        invalidateTextLines();
        
        // We don't need to call invalidateProperties()
        // because the hostFormat and the _textFlow are still valid.

        // This is smart enough not to remeasure if the explicit width/height
        // were specified.
        invalidateSize();
        
        invalidateDisplayList();  
    }    
}

}
